1# Copyright 2016 The Distro Tracker Developers
2# See the COPYRIGHT file at the top-level directory of this distribution and
3# at https://deb.li/DTAuthors
4#
5# This file is part of Distro Tracker. It is subject to the license terms
6# in the LICENSE file found in the top-level directory of this
7# distribution and at https://deb.li/DTLicense. No part of Distro Tracker,
8# including this file, may be copied, modified, propagated, or distributed
9# except according to the terms contained in the LICENSE file.
10"""
11Implements a command to perform various database fixups.
12"""
14from django.core.exceptions import ObjectDoesNotExist
15from django.core.management.base import BaseCommand
16from django.db.models import Count
17from django.db.models.functions import Lower
19from distro_tracker.core.models import EmailSettings, News, UserEmail
22class Command(BaseCommand):
23 """
24 A management command which updates package information found in all
25 registered repositories.
26 """
27 help = "Fix various database inconsistencies" # noqa
29 def handle(self, *args, **kwargs):
30 self.verbose = int(kwargs.get('verbosity', 1)) > 1
31 self.drop_duplicate_user_emails()
32 self.drop_duplicate_news()
34 def write(self, message):
35 if self.verbose: 35 ↛ 36line 35 didn't jump to line 36, because the condition on line 35 was never true
36 self.stdout.write(message)
38 def drop_duplicate_news(self):
39 qs = News.objects.values('package__name', 'datetime_created__date',
40 'title')
41 qs = qs.annotate(count=Count('pk')).filter(count__gt=1)
42 for news in qs:
43 pk_to_delete = list(News.objects.filter(
44 package__name=news['package__name'],
45 datetime_created__date=news['datetime_created__date'],
46 title=news['title']
47 )[1:].values_list('pk', flat=True))
48 self.write('Dropping duplicate news on package {}: {}'.format(
49 news['package__name'], pk_to_delete))
50 News.objects.filter(pk__in=pk_to_delete).delete()
52 def drop_duplicate_user_emails(self):
53 qs = UserEmail.objects.annotate(lower_email=Lower('email'))
54 qs = qs.values('lower_email').annotate(count=Count('lower_email'))
55 qs = qs.filter(count__gt=1)
56 for item in qs:
57 qs = UserEmail.objects.annotate(lower_email=Lower('email'))
58 qs = qs.filter(lower_email=item['lower_email']).order_by('id')
59 all_user_emails = list(qs)
60 target = all_user_emails[0]
61 for source in all_user_emails[1:]:
62 self.write("Merging UserEmail {} into {}...".format(
63 source.email, target.email))
64 if target.user is None and source.user:
65 self.write(" Associating user")
66 target.user = source.user
67 target.save()
68 try:
69 target.emailsettings
70 except ObjectDoesNotExist:
71 self.write(" Adding EmailSettings")
72 EmailSettings.objects.create(user_email=target).save()
73 target.refresh_from_db()
74 self.merge_subscriptions(target, source)
75 source.delete()
77 def merge_subscriptions(self, target, source):
78 try:
79 source.emailsettings
80 except ObjectDoesNotExist:
81 self.write(" {} has no EmailSettings".format(source.email))
82 return
83 self.write(" Moving subscriptions")
84 target_sub = target.emailsettings.subscription_set
85 for sub in source.emailsettings.subscription_set.all():
86 if target_sub.filter(package__name=sub.package.name).count() == 0:
87 self.write(" Moving {} package subscription "
88 "from {} to {}".format(sub.package.name,
89 source.email, target.email))
90 target.emailsettings.subscription_set.add(sub)