[Backport] Adding user creation to user profile timeline

remotes/origin/enhancement/email-actions
Alejandro Alonso 2015-06-03 15:32:24 +02:00 committed by David Barragán Merino
parent 412c25faee
commit de88b0e436
9 changed files with 166 additions and 25 deletions

View File

@ -32,3 +32,5 @@ class TimelineAppConfig(AppConfig):
sender=apps.get_model("projects", "Membership")) sender=apps.get_model("projects", "Membership"))
signals.post_delete.connect(handlers.delete_membership_push_to_timeline, signals.post_delete.connect(handlers.delete_membership_push_to_timeline,
sender=apps.get_model("projects", "Membership")) sender=apps.get_model("projects", "Membership"))
signals.post_save.connect(handlers.create_user_push_to_timeline,
sender=apps.get_model("users", "User"))

View File

@ -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()

View File

@ -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,
),
]

View File

@ -31,7 +31,7 @@ class Timeline(models.Model):
content_object = GenericForeignKey('content_type', 'object_id') content_object = GenericForeignKey('content_type', 'object_id')
namespace = models.CharField(max_length=250, default="default", db_index=True) namespace = models.CharField(max_length=250, default="default", db_index=True)
event_type = models.CharField(max_length=250, 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 = JsonField()
data_content_type = models.ForeignKey(ContentType, related_name="data_timelines") data_content_type = models.ForeignKey(ContentType, related_name="data_timelines")
created = models.DateTimeField(default=timezone.now) created = models.DateTimeField(default=timezone.now)

View File

@ -43,6 +43,7 @@ class TimelineDataJsonField(serializers.WritableField):
"photo": get_photo_or_gravatar_url(user), "photo": get_photo_or_gravatar_url(user),
"big_photo": get_big_photo_or_gravatar_url(user), "big_photo": get_big_photo_or_gravatar_url(user),
"username": user.username, "username": user.username,
"date_joined": user.date_joined,
} }
except User.DoesNotExist: except User.DoesNotExist:
pass pass

View File

@ -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) event_type_key = _get_impl_key_from_model(instance.__class__, event_type)
impl = _timeline_impl_map.get(event_type_key, None) impl = _timeline_impl_map.get(event_type_key, None)
project = None
if hasattr(instance, "project"):
project = instance.project
Timeline.objects.create( Timeline.objects.create(
content_object=obj, content_object=obj,
namespace=namespace, namespace=namespace,
event_type=event_type_key, event_type=event_type_key,
project=instance.project, project=project,
data=impl(instance, extra_data=extra_data), data=impl(instance, extra_data=extra_data),
data_content_type = ContentType.objects.get_for_model(instance.__class__), 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): def filter_timeline_for_user(timeline, user):
# Filtering public projects # Filtering entities from public projects or entities without project
tl_filter = Q(project__is_private=False) tl_filter = Q(project__is_private=False) | Q(project=None)
# Filtering private project with some public parts # Filtering private project with some public parts
content_types = { content_types = {

View File

@ -34,10 +34,11 @@ def _push_to_timeline(*args, **kwargs):
def _push_to_timelines(project, user, obj, event_type, extra_data={}): def _push_to_timelines(project, user, obj, event_type, extra_data={}):
if project is not None:
# Project timeline # Project timeline
_push_to_timeline(project, obj, event_type, _push_to_timeline(project, obj, event_type,
namespace=build_project_namespace(project), namespace=build_project_namespace(project),
extra_data=extra_data) extra_data=extra_data)
# User timeline # User timeline
_push_to_timeline(user, obj, event_type, _push_to_timeline(user, obj, event_type,
@ -56,18 +57,18 @@ def _push_to_timelines(project, user, obj, event_type, extra_data={}):
if watchers: if watchers:
related_people |= watchers related_people |= watchers
# Team if project is not None:
team_members_ids = project.memberships.filter(user__isnull=False).values_list("id", flat=True) # Team
team = User.objects.filter(id__in=team_members_ids) team_members_ids = project.memberships.filter(user__isnull=False).values_list("id", flat=True)
related_people |= team 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, #Related people: team members
namespace=build_user_namespace(user),
extra_data=extra_data)
#Related people: team members
def on_new_history_entry(sender, instance, created, **kwargs): 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): def delete_membership_push_to_timeline(sender, instance, **kwargs):
if instance.user: if instance.user:
_push_to_timelines(instance.project, instance.user, instance, "delete") _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")

View File

@ -105,3 +105,11 @@ def membership_timeline(instance, extra_data={}):
} }
result.update(extra_data) result.update(extra_data)
return result 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

View File

@ -36,7 +36,7 @@ def test_add_to_object_timeline():
service._add_to_object_timeline(user1, task, "test") 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) 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(user1, task4, "test")
service._add_to_object_timeline(user2, task1, "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=user1.id).count() == 5
assert Timeline.objects.filter(object_id=user2.id).count() == 1 assert Timeline.objects.filter(object_id=user2.id).count() == 2
assert Timeline.objects.filter(object_id=user3.id).count() == 0 assert Timeline.objects.filter(object_id=user3.id).count() == 1
def test_filter_timeline_no_privileges(): 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.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, task1, "test")
timeline = Timeline.objects.all() timeline = Timeline.objects.exclude(event_type="users.user.create")
timeline = service.filter_timeline_for_user(timeline, user2) timeline = service.filter_timeline_for_user(timeline, user2)
assert timeline.count() == 0 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.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, task1, "test")
service._add_to_object_timeline(user1, task2, "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) timeline = service.filter_timeline_for_user(timeline, user2)
assert timeline.count() == 1 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.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, task1, "test")
service._add_to_object_timeline(user1, task2, "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) timeline = service.filter_timeline_for_user(timeline, user2)
assert timeline.count() == 1 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.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, task1, "test")
service._add_to_object_timeline(user1, task2, "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) timeline = service.filter_timeline_for_user(timeline, user2)
assert timeline.count() == 3 assert timeline.count() == 3