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""" 

13 

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 

18 

19from distro_tracker.core.models import EmailSettings, News, UserEmail 

20 

21 

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 

28 

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() 

33 

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) 

37 

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() 

51 

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() 

76 

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)