Adding user creation to user profile timeline
parent
1f7ca6cb97
commit
7663e658c1
|
@ -32,3 +32,5 @@ class TimelineAppConfig(AppConfig):
|
|||
sender=apps.get_model("projects", "Membership"))
|
||||
signals.post_delete.connect(handlers.delete_membership_push_to_timeline,
|
||||
sender=apps.get_model("projects", "Membership"))
|
||||
signals.post_save.connect(handlers.create_user_push_to_timeline,
|
||||
sender=apps.get_model("users", "User"))
|
||||
|
|
|
@ -0,0 +1,98 @@
|
|||
# Copyright (C) 2014 Andrey Antukh <niwi@niwi.be>
|
||||
# Copyright (C) 2014 Jesús Espino <jespinog@gmail.com>
|
||||
# Copyright (C) 2014 David Barragán <bameda@dbarragan.com>
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as
|
||||
# published by the Free Software Foundation, either version 3 of the
|
||||
# License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# 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/>.
|
||||
|
||||
# Examples:
|
||||
# python manage.py rebuild_timeline_for_user_creation --settings=settings.local_timeline
|
||||
|
||||
from django.conf import settings
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.core.exceptions import ObjectDoesNotExist
|
||||
from django.core.management.base import BaseCommand
|
||||
from django.db.models import Model
|
||||
from django.db import reset_queries
|
||||
|
||||
from taiga.timeline.service import (_get_impl_key_from_model,
|
||||
_timeline_impl_map, extract_user_info)
|
||||
from taiga.timeline.models import Timeline
|
||||
from taiga.timeline.signals import _push_to_timelines
|
||||
from taiga.users.models import User
|
||||
|
||||
from unittest.mock import patch
|
||||
|
||||
import gc
|
||||
|
||||
class BulkCreator(object):
|
||||
def __init__(self):
|
||||
self.timeline_objects = []
|
||||
self.created = None
|
||||
|
||||
def create_element(self, element):
|
||||
self.timeline_objects.append(element)
|
||||
if len(self.timeline_objects) > 1000:
|
||||
self.flush()
|
||||
|
||||
def flush(self):
|
||||
Timeline.objects.bulk_create(self.timeline_objects, batch_size=1000)
|
||||
del self.timeline_objects
|
||||
self.timeline_objects = []
|
||||
gc.collect()
|
||||
|
||||
bulk_creator = BulkCreator()
|
||||
|
||||
|
||||
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(instance, Model), "instance must be a instance of Model"
|
||||
event_type_key = _get_impl_key_from_model(instance.__class__, event_type)
|
||||
impl = _timeline_impl_map.get(event_type_key, None)
|
||||
|
||||
bulk_creator.create_element(Timeline(
|
||||
content_object=obj,
|
||||
namespace=namespace,
|
||||
event_type=event_type_key,
|
||||
project=None,
|
||||
data=impl(instance, extra_data=extra_data),
|
||||
data_content_type = ContentType.objects.get_for_model(instance.__class__),
|
||||
created = bulk_creator.created,
|
||||
))
|
||||
|
||||
|
||||
def generate_timeline():
|
||||
with patch('taiga.timeline.service._add_to_object_timeline', new=custom_add_to_object_timeline):
|
||||
# Users api wasn't a HistoryResourceMixin so we can't interate on the HistoryEntries in this case
|
||||
users = User.objects.order_by("date_joined")
|
||||
for user in users.iterator():
|
||||
bulk_creator.created = user.date_joined
|
||||
print("User:", user.date_joined)
|
||||
extra_data = {
|
||||
"values_diff": {},
|
||||
"user": extract_user_info(user),
|
||||
}
|
||||
_push_to_timelines(None, user, user, "create", extra_data=extra_data)
|
||||
del extra_data
|
||||
|
||||
bulk_creator.flush()
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = 'Regenerate project timeline'
|
||||
|
||||
def handle(self, *args, **options):
|
||||
debug_enabled = settings.DEBUG
|
||||
if debug_enabled:
|
||||
print("Please, execute this script only with DEBUG mode disabled (DEBUG=False)")
|
||||
return
|
||||
|
||||
generate_timeline()
|
|
@ -0,0 +1,20 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import models, migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('timeline', '0003_auto_20150410_0829'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='timeline',
|
||||
name='project',
|
||||
field=models.ForeignKey(null=True, to='projects.Project'),
|
||||
preserve_default=True,
|
||||
),
|
||||
]
|
|
@ -31,7 +31,7 @@ class Timeline(models.Model):
|
|||
content_object = GenericForeignKey('content_type', 'object_id')
|
||||
namespace = models.CharField(max_length=250, default="default", db_index=True)
|
||||
event_type = models.CharField(max_length=250, db_index=True)
|
||||
project = models.ForeignKey(Project)
|
||||
project = models.ForeignKey(Project, null=True)
|
||||
data = JsonField()
|
||||
data_content_type = models.ForeignKey(ContentType, related_name="data_timelines")
|
||||
created = models.DateTimeField(default=timezone.now)
|
||||
|
|
|
@ -43,6 +43,7 @@ class TimelineDataJsonField(serializers.WritableField):
|
|||
"photo": get_photo_or_gravatar_url(user),
|
||||
"big_photo": get_big_photo_or_gravatar_url(user),
|
||||
"username": user.username,
|
||||
"date_joined": user.date_joined,
|
||||
}
|
||||
except User.DoesNotExist:
|
||||
pass
|
||||
|
|
|
@ -57,11 +57,15 @@ def _add_to_object_timeline(obj:object, instance:object, event_type:str, namespa
|
|||
event_type_key = _get_impl_key_from_model(instance.__class__, event_type)
|
||||
impl = _timeline_impl_map.get(event_type_key, None)
|
||||
|
||||
project = None
|
||||
if hasattr(instance, "project"):
|
||||
project = instance.project
|
||||
|
||||
Timeline.objects.create(
|
||||
content_object=obj,
|
||||
namespace=namespace,
|
||||
event_type=event_type_key,
|
||||
project=instance.project,
|
||||
project=project,
|
||||
data=impl(instance, extra_data=extra_data),
|
||||
data_content_type = ContentType.objects.get_for_model(instance.__class__),
|
||||
)
|
||||
|
@ -96,8 +100,8 @@ def get_timeline(obj, namespace=None):
|
|||
|
||||
|
||||
def filter_timeline_for_user(timeline, user):
|
||||
# Filtering public projects
|
||||
tl_filter = Q(project__is_private=False)
|
||||
# Filtering entities from public projects or entities without project
|
||||
tl_filter = Q(project__is_private=False) | Q(project=None)
|
||||
|
||||
# Filtering private project with some public parts
|
||||
content_types = {
|
||||
|
|
|
@ -34,10 +34,11 @@ def _push_to_timeline(*args, **kwargs):
|
|||
|
||||
|
||||
def _push_to_timelines(project, user, obj, event_type, extra_data={}):
|
||||
if project is not None:
|
||||
# Project timeline
|
||||
_push_to_timeline(project, obj, event_type,
|
||||
namespace=build_project_namespace(project),
|
||||
extra_data=extra_data)
|
||||
_push_to_timeline(project, obj, event_type,
|
||||
namespace=build_project_namespace(project),
|
||||
extra_data=extra_data)
|
||||
|
||||
# User timeline
|
||||
_push_to_timeline(user, obj, event_type,
|
||||
|
@ -56,18 +57,18 @@ def _push_to_timelines(project, user, obj, event_type, extra_data={}):
|
|||
if watchers:
|
||||
related_people |= watchers
|
||||
|
||||
# Team
|
||||
team_members_ids = project.memberships.filter(user__isnull=False).values_list("id", flat=True)
|
||||
team = User.objects.filter(id__in=team_members_ids)
|
||||
related_people |= team
|
||||
if project is not None:
|
||||
# Team
|
||||
team_members_ids = project.memberships.filter(user__isnull=False).values_list("id", flat=True)
|
||||
team = User.objects.filter(id__in=team_members_ids)
|
||||
related_people |= team
|
||||
related_people = related_people.distinct()
|
||||
|
||||
related_people = related_people.distinct()
|
||||
_push_to_timeline(related_people, obj, event_type,
|
||||
namespace=build_user_namespace(user),
|
||||
extra_data=extra_data)
|
||||
|
||||
_push_to_timeline(related_people, obj, event_type,
|
||||
namespace=build_user_namespace(user),
|
||||
extra_data=extra_data)
|
||||
|
||||
#Related people: team members
|
||||
#Related people: team members
|
||||
|
||||
|
||||
def on_new_history_entry(sender, instance, created, **kwargs):
|
||||
|
@ -122,3 +123,10 @@ def create_membership_push_to_timeline(sender, instance, **kwargs):
|
|||
def delete_membership_push_to_timeline(sender, instance, **kwargs):
|
||||
if instance.user:
|
||||
_push_to_timelines(instance.project, instance.user, instance, "delete")
|
||||
|
||||
|
||||
def create_user_push_to_timeline(sender, instance, created, **kwargs):
|
||||
if created:
|
||||
project = None
|
||||
user = instance
|
||||
_push_to_timelines(project, user, user, "create")
|
||||
|
|
|
@ -105,3 +105,11 @@ def membership_timeline(instance, extra_data={}):
|
|||
}
|
||||
result.update(extra_data)
|
||||
return result
|
||||
|
||||
@register_timeline_implementation("users.user", "create")
|
||||
def user_timeline(instance, extra_data={}):
|
||||
result = {
|
||||
"user": service.extract_user_info(instance),
|
||||
}
|
||||
result.update(extra_data)
|
||||
return result
|
||||
|
|
|
@ -36,7 +36,7 @@ def test_add_to_object_timeline():
|
|||
|
||||
service._add_to_object_timeline(user1, task, "test")
|
||||
|
||||
assert Timeline.objects.filter(object_id=user1.id).count() == 1
|
||||
assert Timeline.objects.filter(object_id=user1.id).count() == 2
|
||||
assert Timeline.objects.order_by("-id")[0].data == id(task)
|
||||
|
||||
|
||||
|
@ -59,9 +59,9 @@ def test_get_timeline():
|
|||
service._add_to_object_timeline(user1, task4, "test")
|
||||
service._add_to_object_timeline(user2, task1, "test")
|
||||
|
||||
assert Timeline.objects.filter(object_id=user1.id).count() == 4
|
||||
assert Timeline.objects.filter(object_id=user2.id).count() == 1
|
||||
assert Timeline.objects.filter(object_id=user3.id).count() == 0
|
||||
assert Timeline.objects.filter(object_id=user1.id).count() == 5
|
||||
assert Timeline.objects.filter(object_id=user2.id).count() == 2
|
||||
assert Timeline.objects.filter(object_id=user3.id).count() == 1
|
||||
|
||||
|
||||
def test_filter_timeline_no_privileges():
|
||||
|
@ -72,7 +72,7 @@ def test_filter_timeline_no_privileges():
|
|||
|
||||
service.register_timeline_implementation("tasks.task", "test", lambda x, extra_data=None: str(id(x)))
|
||||
service._add_to_object_timeline(user1, task1, "test")
|
||||
timeline = Timeline.objects.all()
|
||||
timeline = Timeline.objects.exclude(event_type="users.user.create")
|
||||
timeline = service.filter_timeline_for_user(timeline, user2)
|
||||
assert timeline.count() == 0
|
||||
|
||||
|
@ -88,7 +88,7 @@ def test_filter_timeline_public_project():
|
|||
service.register_timeline_implementation("tasks.task", "test", lambda x, extra_data=None: str(id(x)))
|
||||
service._add_to_object_timeline(user1, task1, "test")
|
||||
service._add_to_object_timeline(user1, task2, "test")
|
||||
timeline = Timeline.objects.all()
|
||||
timeline = Timeline.objects.exclude(event_type="users.user.create")
|
||||
timeline = service.filter_timeline_for_user(timeline, user2)
|
||||
assert timeline.count() == 1
|
||||
|
||||
|
@ -104,7 +104,7 @@ def test_filter_timeline_private_project_anon_permissions():
|
|||
service.register_timeline_implementation("tasks.task", "test", lambda x, extra_data=None: str(id(x)))
|
||||
service._add_to_object_timeline(user1, task1, "test")
|
||||
service._add_to_object_timeline(user1, task2, "test")
|
||||
timeline = Timeline.objects.all()
|
||||
timeline = Timeline.objects.exclude(event_type="users.user.create")
|
||||
timeline = service.filter_timeline_for_user(timeline, user2)
|
||||
assert timeline.count() == 1
|
||||
|
||||
|
@ -123,7 +123,7 @@ def test_filter_timeline_private_project_member_permissions():
|
|||
service.register_timeline_implementation("tasks.task", "test", lambda x, extra_data=None: str(id(x)))
|
||||
service._add_to_object_timeline(user1, task1, "test")
|
||||
service._add_to_object_timeline(user1, task2, "test")
|
||||
timeline = Timeline.objects.all()
|
||||
timeline = Timeline.objects.exclude(event_type="users.user.create")
|
||||
timeline = service.filter_timeline_for_user(timeline, user2)
|
||||
assert timeline.count() == 3
|
||||
|
||||
|
|
Loading…
Reference in New Issue