[backport] Improving rebuild timeline command
parent
39311d9004
commit
57aee955f1
|
@ -14,7 +14,13 @@
|
||||||
# You should have received a copy of the GNU Affero General Public License
|
# You should have received a copy of the GNU Affero General Public License
|
||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
# Examples:
|
||||||
|
# python manage.py rebuild_timeline --settings=settings.local_timeline --initial_date 2014-10-02 --final_date 2014-10-03
|
||||||
|
# python manage.py rebuild_timeline --settings=settings.local_timeline --purge
|
||||||
|
# python manage.py rebuild_timeline --settings=settings.local_timeline --initial_date 2014-10-02
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
from django.contrib.contenttypes.models import ContentType
|
||||||
from django.core.exceptions import ObjectDoesNotExist
|
from django.core.exceptions import ObjectDoesNotExist
|
||||||
from django.core.management.base import BaseCommand
|
from django.core.management.base import BaseCommand
|
||||||
from django.db.models import Model
|
from django.db.models import Model
|
||||||
|
@ -31,7 +37,7 @@ from taiga.timeline.signals import on_new_history_entry, _push_to_timelines
|
||||||
from taiga.users.models import User
|
from taiga.users.models import User
|
||||||
|
|
||||||
from unittest.mock import patch
|
from unittest.mock import patch
|
||||||
from django.contrib.contenttypes.models import ContentType
|
from optparse import make_option
|
||||||
|
|
||||||
import gc
|
import gc
|
||||||
|
|
||||||
|
@ -51,26 +57,6 @@ class BulkCreator(object):
|
||||||
bulk_creator = BulkCreator()
|
bulk_creator = BulkCreator()
|
||||||
|
|
||||||
|
|
||||||
def queryset_iterator(queryset, chunksize=1000):
|
|
||||||
'''''
|
|
||||||
Iterate over a Django Queryset ordered by the primary key
|
|
||||||
|
|
||||||
This method loads a maximum of chunksize (default: 1000) rows in it's
|
|
||||||
memory at the same time while django normally would load all rows in it's
|
|
||||||
memory. Using the iterator() method only causes it to not preload all the
|
|
||||||
classes.
|
|
||||||
|
|
||||||
Note that the implementation of the iterator does not support ordered query sets.
|
|
||||||
'''
|
|
||||||
queryset = queryset.order_by('pk')
|
|
||||||
pk = queryset[0].pk
|
|
||||||
last_pk = queryset.order_by('-pk')[0].pk
|
|
||||||
while pk < last_pk:
|
|
||||||
for row in queryset.filter(pk__gt=pk)[:chunksize]:
|
|
||||||
pk = row.pk
|
|
||||||
yield row
|
|
||||||
gc.collect()
|
|
||||||
|
|
||||||
def custom_add_to_object_timeline(obj:object, instance:object, event_type:str, namespace:str="default", extra_data:dict={}):
|
def custom_add_to_object_timeline(obj:object, instance:object, event_type:str, namespace:str="default", extra_data:dict={}):
|
||||||
assert isinstance(obj, Model), "obj must be a instance of Model"
|
assert isinstance(obj, Model), "obj must be a instance of Model"
|
||||||
assert isinstance(instance, Model), "instance must be a instance of Model"
|
assert isinstance(instance, Model), "instance must be a instance of Model"
|
||||||
|
@ -88,10 +74,30 @@ def custom_add_to_object_timeline(obj:object, instance:object, event_type:str, n
|
||||||
))
|
))
|
||||||
|
|
||||||
|
|
||||||
def generate_timeline():
|
def generate_timeline(initial_date, final_date):
|
||||||
|
if initial_date or final_date:
|
||||||
|
timelines = Timeline.objects.all()
|
||||||
|
if initial_date:
|
||||||
|
timelines = timelines.filter(created__gte=initial_date)
|
||||||
|
if final_date:
|
||||||
|
timelines = timelines.filter(created__lt=final_date)
|
||||||
|
|
||||||
|
timelines.delete()
|
||||||
|
|
||||||
with patch('taiga.timeline.service._add_to_object_timeline', new=custom_add_to_object_timeline):
|
with patch('taiga.timeline.service._add_to_object_timeline', new=custom_add_to_object_timeline):
|
||||||
# Projects api wasn't a HistoryResourceMixin so we can't interate on the HistoryEntries in this case
|
# Projects api wasn't a HistoryResourceMixin so we can't interate on the HistoryEntries in this case
|
||||||
for project in queryset_iterator(Project.objects.order_by("created_date")):
|
projects = Project.objects.order_by("created_date")
|
||||||
|
history_entries = HistoryEntry.objects.order_by("created_at")
|
||||||
|
|
||||||
|
if initial_date:
|
||||||
|
projects = projects.filter(created_date__gte=initial_date)
|
||||||
|
history_entries = history_entries.filter(created_at__gte=initial_date)
|
||||||
|
|
||||||
|
if final_date:
|
||||||
|
projects = projects.filter(created_date__lt=final_date)
|
||||||
|
history_entries = history_entries.filter(created_at__lt=final_date)
|
||||||
|
|
||||||
|
for project in projects.iterator():
|
||||||
bulk_creator.created = project.created_date
|
bulk_creator.created = project.created_date
|
||||||
print("Project:", bulk_creator.created)
|
print("Project:", bulk_creator.created)
|
||||||
extra_data = {
|
extra_data = {
|
||||||
|
@ -101,7 +107,7 @@ def generate_timeline():
|
||||||
_push_to_timelines(project, project.owner, project, "create", extra_data=extra_data)
|
_push_to_timelines(project, project.owner, project, "create", extra_data=extra_data)
|
||||||
del extra_data
|
del extra_data
|
||||||
|
|
||||||
for historyEntry in queryset_iterator(HistoryEntry.objects.order_by("created_at")):
|
for historyEntry in history_entries.iterator():
|
||||||
print("History entry:", historyEntry.created_at)
|
print("History entry:", historyEntry.created_at)
|
||||||
try:
|
try:
|
||||||
bulk_creator.created = historyEntry.created_at
|
bulk_creator.created = historyEntry.created_at
|
||||||
|
@ -111,11 +117,35 @@ def generate_timeline():
|
||||||
|
|
||||||
|
|
||||||
class Command(BaseCommand):
|
class Command(BaseCommand):
|
||||||
|
help = 'Regenerate project timeline'
|
||||||
|
option_list = BaseCommand.option_list + (
|
||||||
|
make_option('--purge',
|
||||||
|
action='store_true',
|
||||||
|
dest='purge',
|
||||||
|
default=False,
|
||||||
|
help='Purge existing timelines'),
|
||||||
|
) + (
|
||||||
|
make_option('--initial_date',
|
||||||
|
action='store',
|
||||||
|
dest='initial_date',
|
||||||
|
default=None,
|
||||||
|
help='Initial date for timeline generation'),
|
||||||
|
) + (
|
||||||
|
make_option('--final_date',
|
||||||
|
action='store',
|
||||||
|
dest='final_date',
|
||||||
|
default=None,
|
||||||
|
help='Final date for timeline generation'),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def handle(self, *args, **options):
|
def handle(self, *args, **options):
|
||||||
debug_enabled = settings.DEBUG
|
debug_enabled = settings.DEBUG
|
||||||
if debug_enabled:
|
if debug_enabled:
|
||||||
print("Please, execute this script only with DEBUG mode disabled (DEBUG=False)")
|
print("Please, execute this script only with DEBUG mode disabled (DEBUG=False)")
|
||||||
return
|
return
|
||||||
|
|
||||||
|
if options["purge"] == True:
|
||||||
Timeline.objects.all().delete()
|
Timeline.objects.all().delete()
|
||||||
generate_timeline()
|
|
||||||
|
generate_timeline(options["initial_date"], options["final_date"])
|
||||||
|
|
Loading…
Reference in New Issue