1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

# Copyright 2016 The Distro Tracker Developers 

# See the COPYRIGHT file at the top-level directory of this distribution and 

# at https://deb.li/DTAuthors 

# 

# This file is part of Distro Tracker. It is subject to the license terms 

# in the LICENSE file found in the top-level directory of this 

# distribution and at https://deb.li/DTLicense. No part of Distro Tracker, 

# including this file, may be copied, modified, propagated, or distributed 

# except according to the terms contained in the LICENSE file. 

""" 

Implements a command to perform various database fixups. 

""" 

 

from django.core.exceptions import ObjectDoesNotExist 

from django.core.management.base import BaseCommand 

from django.db.models import Count 

from django.db.models.functions import Lower 

 

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

 

 

class Command(BaseCommand): 

""" 

A management command which updates package information found in all 

registered repositories. 

""" 

help = "Fix various database inconsistencies" # noqa 

 

def handle(self, *args, **kwargs): 

self.verbose = int(kwargs.get('verbosity', 1)) > 1 

self.drop_duplicate_user_emails() 

self.drop_duplicate_news() 

 

def write(self, message): 

35 ↛ 36line 35 didn't jump to line 36, because the condition on line 35 was never true if self.verbose: 

self.stdout.write(message) 

 

def drop_duplicate_news(self): 

qs = News.objects.values('package__name', 'datetime_created__date', 

'title') 

qs = qs.annotate(count=Count('pk')).filter(count__gt=1) 

for news in qs: 

pk_to_delete = list(News.objects.filter( 

package__name=news['package__name'], 

datetime_created__date=news['datetime_created__date'], 

title=news['title'] 

)[1:].values_list('pk', flat=True)) 

self.write('Dropping duplicate news on package {}: {}'.format( 

news['package__name'], pk_to_delete)) 

News.objects.filter(pk__in=pk_to_delete).delete() 

 

def drop_duplicate_user_emails(self): 

qs = UserEmail.objects.annotate(lower_email=Lower('email')) 

qs = qs.values('lower_email').annotate(count=Count('lower_email')) 

qs = qs.filter(count__gt=1) 

for item in qs: 

qs = UserEmail.objects.annotate(lower_email=Lower('email')) 

qs = qs.filter(lower_email=item['lower_email']).order_by('id') 

all_user_emails = list(qs) 

target = all_user_emails[0] 

for source in all_user_emails[1:]: 

self.write("Merging UserEmail {} into {}...".format( 

source.email, target.email)) 

if target.user is None and source.user: 

self.write(" Associating user") 

target.user = source.user 

target.save() 

try: 

target.emailsettings 

except ObjectDoesNotExist: 

self.write(" Adding EmailSettings") 

EmailSettings.objects.create(user_email=target).save() 

target.refresh_from_db() 

self.merge_subscriptions(target, source) 

source.delete() 

 

def merge_subscriptions(self, target, source): 

try: 

source.emailsettings 

except ObjectDoesNotExist: 

self.write(" {} has no EmailSettings".format(source.email)) 

return 

self.write(" Moving subscriptions") 

target_sub = target.emailsettings.subscription_set 

for sub in source.emailsettings.subscription_set.all(): 

if target_sub.filter(package__name=sub.package.name).count() == 0: 

self.write(" Moving {} package subscription " 

"from {} to {}".format(sub.package.name, 

source.email, target.email)) 

target.emailsettings.subscription_set.add(sub)