Repository: translate/pootle

Issue:
merge_user and no source_wordcount
This occured when running `./manage.py merge_user GTAdmin Luganda` on mlo data.

``` pytb
Merging user: GTAdmin --> Luganda...
* Merging units submitted_by: GTAdmin --> Luganda... DONE
 * Merging units comments: GTAdmin --> Luganda... DONE
 * Merging units reviewed: GTAdmin --> Luganda... DONE
 * Merging remaining submissions: GTAdmin --> Luganda... FAILED
FAILED
Traceback (most recent call last):                                                                                                       
  File "./manage.py", line 22, in <module>
    execute_from_command_line()  File "/var/www/sites/mozilla/env/local/lib/python2.7/site-packages/django/core/management/__init__.py", line 385, in execute_from_command_line
    utility.execute()
  File "/var/www/sites/mozilla/env/local/lib/python2.7/site-packages/django/core/management/__init__.py", line 377, in execute
    self.fetch_command(subcommand).run_from_argv(self.argv)
  File "/var/www/sites/mozilla/env/local/lib/python2.7/site-packages/django/core/management/base.py", line 288, in run_from_argv
    self.execute(*args, **options.__dict__)
  File "/var/www/sites/mozilla/env/local/lib/python2.7/site-packages/raven/contrib/django/management/__init__.py", line 41, in new_execute
    return original_func(self, *args, **kwargs)
  File "/var/www/sites/mozilla/env/local/lib/python2.7/site-packages/django/core/management/base.py", line 338, in execute
    output = self.handle(*args, **options)
  File "/var/www/sites/mozilla/src/pootle/apps/accounts/management/commands/merge_user.py", line 22, in handle
    self.get_user(username=args[1])).merge()
  File "/var/www/sites/mozilla/src/pootle/apps/accounts/utils.py", line 59, in method_wrapper
    raise e
AttributeError: 'NoneType' object has no attribute 'source_wordcount'
```



Discussion:

Resolving patch:
diff --git a/pootle/apps/accounts/utils.py b/pootle/apps/accounts/utils.py
--- a/pootle/apps/accounts/utils.py
+++ b/pootle/apps/accounts/utils.py
@@ -56,6 +56,7 @@ def method_wrapper(self, *args, **kwargs):
                 f(self, *args, **kwargs)
             except Exception as e:
                 sys.stdout.write(fail_msg % self.__dict__)
+                logger.exception(e)
                 raise e
             sys.stdout.write(end_msg % self.__dict__)
         return method_wrapper
@@ -96,58 +97,43 @@ def merge(self):
     def merge_commented(self):
         """Merge commented_by attribute on units
         """
-        for unit in self.src_user.commented.iterator():
-            unit.commented_by = self.target_user
-            unit.save()
-            logger.debug("Unit commented_by updated: %s" % repr(unit))
+        self.src_user.commented.update(commented_by=self.target_user)
 
     @write_stdout(" * Merging units reviewed: "
                   "%(src_user)s --> %(target_user)s... ")
     def merge_reviewed(self):
         """Merge reviewed_by attribute on units
         """
-        # Update submitted_by, commented_by and reviewed_by on units
-        for unit in self.src_user.reviewed.iterator():
-            unit.reviewed_by = self.target_user
-            unit.save()
-            logger.debug("Unit reviewed_by updated: %s" % repr(unit))
+        self.src_user.reviewed.update(reviewed_by=self.target_user)
 
     @write_stdout(" * Merging suggestion reviews: "
                   "%(src_user)s --> %(target_user)s... ")
     def merge_reviews(self):
         """Merge reviewer attribute on suggestions
         """
-        for suggestion in self.src_user.reviews.iterator():
-            suggestion.reviewer = self.target_user
-            suggestion.save()
-            logger.debug("Suggestion reviewer updated: %s" % suggestion)
+        self.src_user.reviews.update(reviewer=self.target_user)
 
     @write_stdout(" * Merging remaining submissions: "
                   "%(src_user)s --> %(target_user)s... ")
     def merge_submissions(self):
         """Merge submitter attribute on submissions
         """
-        # Update submitter on submissions
-        for submission in self.src_user.submission_set.iterator():
-            submission.submitter = self.target_user
+        # Delete orphaned submissions.
+        self.src_user.submission_set.filter(unit__isnull=True).delete()
 
-            # Before we can save we first have to remove existing score_logs
-            # for this submission - they will be recreated on save with correct
-            # user.
-            for score_log in submission.scorelog_set.iterator():
-                score_log.delete()
-            submission.save()
-            logger.debug("Submission submitter updated: %s" % submission)
+        # Before we can save we first have to remove existing score_logs
+        # for src_user - they will be recreated on save for target_user
+        self.src_user.scorelog_set.all().delete()
+
+        # Update submitter on submissions
+        self.src_user.submission_set.update(submitter=self.target_user)
 
     @write_stdout(" * Merging units submitted_by: "
                   "%(src_user)s --> %(target_user)s... ")
     def merge_submitted(self):
         """Merge submitted_by attribute on units
         """
-        for unit in self.src_user.submitted.iterator():
-            unit.submitted_by = self.target_user
-            unit.save()
-            logger.debug("Unit submitted_by updated: %s" % repr(unit))
+        self.src_user.submitted.update(submitted_by=self.target_user)
 
     @write_stdout(" * Merging suggestions: "
                   "%(src_user)s --> %(target_user)s... ")
@@ -155,10 +141,7 @@ def merge_suggestions(self):
         """Merge user attribute on suggestions
         """
         # Update user and reviewer on suggestions
-        for suggestion in self.src_user.suggestions.iterator():
-            suggestion.user = self.target_user
-            suggestion.save()
-            logger.debug("Suggestion user updated: %s" % suggestion)
+        self.src_user.suggestions.update(user=self.target_user)
 
 
 class UserPurger(object):
@@ -190,14 +173,12 @@ def purge(self):
         self.revert_units_state_changed()
 
         # Delete remaining submissions.
-        for submission in self.user.submission_set.iterator():
-            submission.delete()
-            logger.debug("Submission deleted: %s" % submission)
+        logger.debug("Deleting remaining submissions for: %s" % self.user)
+        self.user.submission_set.all().delete()
 
         # Delete remaining suggestions.
-        for suggestion in self.user.suggestions.iterator():
-            suggestion.delete()
-            logger.debug("Suggestion deleted: %s" % suggestion)
+        logger.debug("Deleting remaining suggestions for: %s" % self.user)
+        self.user.suggestions.all().delete()
 
     @write_stdout(" * Removing units created by: %(user)s... ")
     def remove_units_created(self):
@@ -323,6 +304,10 @@ def revert_units_reviewed(self):
     def revert_units_state_changed(self):
         """Revert unit edits made by a user to previous edit.
         """
+
+        # Delete orphaned submissions.
+        self.user.submission_set.filter(unit__isnull=True).delete()
+
         for submission in self.user.get_unit_states_changed().iterator():
             unit = submission.unit
 


Test patch: