Repository: akvo/akvo-rsr

Issue:
Empty projects
Similar to #1753, we should add a management command for deleting empty and stale projects. 

We should remove all non-published projects without any data (except a partnership, which will usually be present) older than a week.



Discussion:

Resolving patch:
diff --git a/akvo/rsr/management/commands/project_cleanup.py b/akvo/rsr/management/commands/project_cleanup.py
new file mode 100644
--- /dev/null
+++ b/akvo/rsr/management/commands/project_cleanup.py
@@ -0,0 +1,74 @@
+# -*- coding: utf-8 -*-
+
+# Akvo Reporting is covered by the GNU Affero General Public License.
+# See more details in the license.txt file located at the root folder of the Akvo RSR module.
+# For additional details on the GNU license please see < http://www.gnu.org/licenses/agpl.html >.
+
+from django.core.management.base import BaseCommand
+from optparse import make_option
+from django.utils import timezone
+from datetime import timedelta
+from ...models import Project, PublishingStatus
+
+
+class Command(BaseCommand):
+
+    args = ''
+    help = 'Script for cleaning up empty projects'
+
+    option_list = BaseCommand.option_list + (
+        make_option('-n', '--num-days',
+                    action='store', dest='num_days',
+                    default=7,
+                    help='Filter projects older than \'n\' days'),
+        make_option('-d', '--delete',
+                    action='store_true', dest='delete',
+                    default=False,
+                    help='Delete filtered projects in addition to listing them'),
+        )
+
+    def handle(self, *args, **options):
+        
+        # parse options
+        verbosity = int(options['verbosity'])
+        delete = bool(options['delete'])
+        num_days = int(options['num_days'])
+
+        # set filter date
+        filter_date = timezone.now() - timedelta(days=num_days)
+        if verbosity > 1: self.stdout.write('Pruning date: {0}'.format(str(filter_date)))
+
+        # filter empty projects
+        if verbosity > 0:
+            self.stdout.write('Filtering all empty projects older than {0} days.'.format(
+                str(num_days)))
+
+        filter_projects = Project.objects.filter(
+            publishingstatus__status=PublishingStatus.STATUS_UNPUBLISHED
+        ).exclude(created_at__gt=filter_date)
+
+        empty_projects = [p for p in filter_projects if p.is_empty()]
+
+        for n, p in enumerate(empty_projects):
+            if verbosity > 1:
+                self.stdout.write('- [{0}/{1}] Empty project by {2} (created {3}) '.format(
+                    str(n + 1),
+                    str(len(empty_projects)),
+                    p.primary_organisation.name.encode('ascii', 'ignore'),
+                    str(p.created_at)
+                ))
+
+            if delete:
+                p.delete()
+
+        # delete filtered projects
+        if delete:
+            if verbosity > 0:
+                self.stdout.write(
+                    '{0} projects(s) matched filter and were successfully removed.'.format(
+                        str(len(empty_projects))))
+
+        elif verbosity > 0:
+            self.stdout.write(
+                '{0} projects(s) matched filter, use \'-d\' flag to remove them.'.format(
+                    str(len(empty_projects))))
diff --git a/akvo/rsr/models/project.py b/akvo/rsr/models/project.py
--- a/akvo/rsr/models/project.py
+++ b/akvo/rsr/models/project.py
@@ -893,6 +893,23 @@ def is_published(self):
         return False
     is_published.boolean = True
 
+    def is_empty(self):
+        exclude_fields = ['benchmarks', 'categories', 'created_at', 'crsadd', 'currency',
+                          'custom_fields', 'fss', 'iati_checks', 'iati_project_exports',
+                          'iatiexport', 'iatiimportjob', 'id', 'is_impact_project', 'is_public',
+                          'last_modified_at', 'partners', 'partnerships', 'paymentgatewayselector',
+                          'primary_organisation', 'primary_organisation_id', 'publishingstatus',
+                          'status', 'validations']
+
+        for field in Project._meta.get_all_field_names():
+            if field not in exclude_fields:
+                field_value = getattr(self, field)
+                m2m_field = getattr(field_value, 'all', None)
+                if (m2m_field and m2m_field()) or (not m2m_field and getattr(self, field)):
+                    return False
+
+        return True
+
     def akvopedia_links(self):
         return self.links.filter(kind=Link.LINK_AKVOPEDIA)
 


Test patch: