Skip to content
This repository has been archived by the owner on Jul 22, 2022. It is now read-only.

First pass at activitystreams #28

Merged
merged 8 commits into from
Oct 31, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 0 additions & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,6 @@ Templates:
Markdown:

* 4 space indent
* 80 character line-length limit
* Continued lines in lists indented so that the first character is vertically
in line with the first character (not list item signifier) on previous
line. See the source of this doc for examples.
Expand Down
7 changes: 6 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
APPLICATIONS := admin core promotion publishers social submissions usermgmt
APPLICATIONS := activitystream administration core promotion publishers social submissions usermgmt
APPLICATIONS_COMMA := $(shell echo $(APPLICATIONS) | tr ' ' ',')

.PHONY: run
Expand Down Expand Up @@ -33,6 +33,7 @@ cleanmigrations: venv/bin/django-admin
@echo "In case @makyo does not delete this before first alpha, do not"
@echo "run this target. Migrations are hecka important for dev after"
@echo "that point!"
exit 1 # We really shouldn't do this, but may need to in the future
@echo
@echo "Psst, @makyo, don't forget to delete this target!"
@sleep 5
Expand All @@ -41,6 +42,10 @@ cleanmigrations: venv/bin/django-admin
touch $$i/migrations/__init__.py; \
done

.PHONY: update-revno
update-revno: venv/bin/django-admin
venv/bin/python manage.py git_revno $(TAG)

.PHONY: test
test:
tox
Expand Down
11 changes: 11 additions & 0 deletions RELEASE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# Release Process

* Ensure that the project is clean across versions by running `make check`
* Tag your release
* ensure master is up to date: `git pull upstream master`
* update the revno file: `TAG=<new tag> make update-revno`
* relying on semver as an outline, make an empty commit specifying the release: `git commit -am "Releasing <new tag>"`
* tag and sign the release: `git tag -s <new tag>`
* Push the new tag
* push the changes: `git push upstream master`
* push the tags: `git push --tags upstream`
1 change: 1 addition & 0 deletions activitystream/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
default_app_config = 'activitystream.apps.ActivitystreamConfig'
File renamed without changes.
10 changes: 10 additions & 0 deletions activitystream/apps.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
from __future__ import unicode_literals

from django.apps import AppConfig


class ActivitystreamConfig(AppConfig):
name = 'activitystream'

def ready(self):
import activitystream.signals # noqa: F401
31 changes: 31 additions & 0 deletions activitystream/migrations/0001_initial.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.2 on 2016-10-30 04:16
from __future__ import unicode_literals

from django.db import migrations, models
import django.db.models.deletion


class Migration(migrations.Migration):

initial = True

dependencies = [
('contenttypes', '0002_remove_content_type_name'),
]

operations = [
migrations.CreateModel(
name='Activity',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('activity_time', models.DateTimeField(auto_now_add=True)),
('activity_type', models.CharField(choices=[('USER:REG', 'User: registered'), ('USER:LOGIN', 'User: logged in'), ('USER:PWCHANGE', 'User: password changed'), ('USER:PWRESET', 'User: password reset'), ('USER:UPDATE', 'User: profile updated'), ('USER:VIEW', 'User: profile viewed'), ('GROUP:CREATE', 'Group: created'), ('GROUP:UPDATE', 'Group: updated'), ('GROUP:DELETE', 'Group: deleted'), ('GROUP:VIEW', 'Group: viewed'), ('SOCIAL:WATCH', 'Social: watch user'), ('SOCIAL:UNWATCH', 'Social: unwatch user'), ('SOCIAL:BLOCK', 'Social: block user'), ('SOCIAL:UNBLOCK', 'Social: unblock user'), ('SOCIAL:FAVORITE', 'Social: favorite submission'), ('SOCIAL:UNFAVORITE', 'Social: unfavorite submission'), ('SOCIAL:RATE', 'Social: rate submission'), ('SOCIAL:ENJOY', 'Social: enjoy submission'), ('SOCIAL:COMMENT', 'Social: object received comment'), ('SUBMISSION:CREATE', 'Submission: created'), ('SUBMISSION:UPDATE', 'Submission: updated'), ('SUBMISSION:DELETE', 'Submission: deleted'), ('SUBMISSION:VIEW', 'Submission: viewed'), ('FOLDER:CREATE', 'Folder: created'), ('FOLDER:UPDATE', 'Folder: updated'), ('FOLDER:DELETE', 'Folder: deleted'), ('FOLDER:VIEW', 'Folder: viewed'), ('FOLDER:SORT', 'Folder: sorted')], max_length=3)),
('object_id', models.PositiveIntegerField()),
('content_type', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='contenttypes.ContentType')),
],
options={
'ordering': ['-activity_type'],
},
),
]
24 changes: 24 additions & 0 deletions activitystream/migrations/0002_auto_20161030_0719.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.2 on 2016-10-30 07:19
from __future__ import unicode_literals

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('activitystream', '0001_initial'),
]

operations = [
migrations.AlterModelOptions(
name='activity',
options={'ordering': ['-activity_time']},
),
migrations.AlterField(
model_name='activity',
name='activity_type',
field=models.CharField(choices=[('USER:REG', 'User: registered'), ('USER:LOGIN', 'User: logged in'), ('USER:LOGOUT', 'User: logged out'), ('USER:PWCHANGE', 'User: password changed'), ('USER:PWRESET', 'User: password reset'), ('PROFILE:UPDATE', 'User: profile updated'), ('PROFILE:VIEW', 'User: profile viewed'), ('ADMINFLAG:CREATE', 'Administration flag: created'), ('ADMINFLAG:UPDATE', 'Administration flag: updated'), ('ADMINFLAG:DELETE', 'Administration flag: deleted'), ('ADMINFLAG:VIEW', 'Administration flag: viewed'), ('GROUP:CREATE', 'Group: created'), ('GROUP:UPDATE', 'Group: updated'), ('GROUP:DELETE', 'Group: deleted'), ('GROUP:VIEW', 'Group: viewed'), ('SOCIAL:WATCH', 'Social: watch user'), ('SOCIAL:UNWATCH', 'Social: unwatch user'), ('SOCIAL:BLOCK', 'Social: block user'), ('SOCIAL:UNBLOCK', 'Social: unblock user'), ('SOCIAL:FAVORITE', 'Social: favorite submission'), ('SOCIAL:UNFAVORITE', 'Social: unfavorite submission'), ('SOCIAL:RATE', 'Social: rate submission'), ('SOCIAL:ENJOY', 'Social: enjoy submission'), ('SUBMISSION:CREATE', 'Submission: created'), ('SUBMISSION:UPDATE', 'Submission: updated'), ('SUBMISSION:DELETE', 'Submission: deleted'), ('SUBMISSION:VIEW', 'Submission: viewed'), ('FOLDER:CREATE', 'Folder: created'), ('FOLDER:UPDATE', 'Folder: updated'), ('FOLDER:DELETE', 'Folder: deleted'), ('FOLDER:VIEW', 'Folder: viewed'), ('FOLDER:SORT', 'Folder: sorted'), ('TAG:CREATE', 'Tag: tag created'), ('TAG:TAG', 'Tag: tagged item created'), ('COMMENT:CREATE', 'Comment: created'), ('COMMENT:UPDATE', 'Comment: updated'), ('COMMENT:DELETE', 'Comment: deleted'), ('PROMOTION:CREATE', 'Promotion: created'), ('PROMOTION:RETIRED', 'Promotion: retired'), ('PUBLISHER:CREATE', 'Publisher: created'), ('PUBLISHER:UPDATE', 'Publisher: updated'), ('PUBLISHER:DELETE', 'Publisher: deleted'), ('PUBLISHER:VIEW', 'Publisher: viewed'), ('PUBLISHER:CLAIMED', 'Publisher: claimed'), ('SEARCH:SEARCH', 'Search: run')], max_length=50),
),
]
20 changes: 20 additions & 0 deletions activitystream/migrations/0003_auto_20161030_0723.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.2 on 2016-10-30 07:23
from __future__ import unicode_literals

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('activitystream', '0002_auto_20161030_0719'),
]

operations = [
migrations.AlterField(
model_name='activity',
name='object_id',
field=models.PositiveIntegerField(blank=True),
),
]
21 changes: 21 additions & 0 deletions activitystream/migrations/0004_auto_20161030_0723.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.2 on 2016-10-30 07:23
from __future__ import unicode_literals

from django.db import migrations, models
import django.db.models.deletion


class Migration(migrations.Migration):

dependencies = [
('activitystream', '0003_auto_20161030_0723'),
]

operations = [
migrations.AlterField(
model_name='activity',
name='content_type',
field=models.ForeignKey(blank=True, on_delete=django.db.models.deletion.CASCADE, to='contenttypes.ContentType'),
),
]
26 changes: 26 additions & 0 deletions activitystream/migrations/0005_auto_20161030_0724.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.2 on 2016-10-30 07:24
from __future__ import unicode_literals

from django.db import migrations, models
import django.db.models.deletion


class Migration(migrations.Migration):

dependencies = [
('activitystream', '0004_auto_20161030_0723'),
]

operations = [
migrations.AlterField(
model_name='activity',
name='content_type',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='contenttypes.ContentType'),
),
migrations.AlterField(
model_name='activity',
name='object_id',
field=models.PositiveIntegerField(blank=True, null=True),
),
]
File renamed without changes.
99 changes: 99 additions & 0 deletions activitystream/models.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
from __future__ import unicode_literals

from django.db import models
from django.contrib.contenttypes.fields import GenericForeignKey
from django.contrib.contenttypes.models import ContentType


class Activity(models.Model):
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add an ActivityAggregateDay that boils down a day of activity of a certain type for a certain object; add a command that generates these once a day from activities so that graphs can query in a more lightweight fashion; activity rotate will leave these so we have graphable data going back forever

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Issue #29

ACTIVITY_TYPES = (
# Users and profiles
('user:reg', 'user: registered'),
('user:login', 'user: logged in'),
('user:logout', 'user: logged out'),
('user:pwchange', 'user: password changed'), # TODO
('user:pwreset', 'user: password reset'), # TODO
('profile:update', 'user: profile updated'),
('profile:view', 'user: profile viewed'),

# Administration flags
('adminflag:create', 'administration flag: created'),
('adminflag:update', 'administration flag: updated'),
('adminflag:delete', 'administration flag: deleted'),
('adminflag:view', 'administration flag: viewed'),

# User groups
('group:create', 'group: created'),
('group:update', 'group: updated'),
('group:delete', 'group: deleted'),

# Social interactions
('social:watch', 'social: watch user'),
('social:unwatch', 'social: unwatch user'),
('social:block', 'social: block user'),
('social:unblock', 'social: unblock user'),
('social:message', 'social: message user'),
('social:favorite', 'social: favorite submission'),
('social:unfavorite', 'social: unfavorite submission'),
('social:rate', 'social: rate submission'),
('social:enjoy', 'social: enjoy submission'),

# Submissions
('submission:create', 'submission: created'),
('submission:update', 'submission: updated'),
('submission:delete', 'submission: deleted'),
('submission:view', 'submission: viewed'),

# Submission folders
('folder:create', 'folder: created'),
('folder:update', 'folder: updated'),
('folder:delete', 'folder: deleted'),
('folder:view', 'folder: viewed'),
('folder:sort', 'folder: sorted'),

# Tags
('tag:create', 'tag: tag created'),
('tag:tag', 'tag: tagged item created'),

# Comments
('comment:create', 'comment: created'),
('comment:update', 'comment: updated'),
('comment:delete', 'comment: deleted'),

# Promotions
('promotion:create', 'promotion: created'),
('promotion:retire', 'promotion: retired'),
('ad:create', 'ad: created,'),
('ad:update', 'ad: update'),
('ad:golive', 'ad: went live'),
('ad:retire', 'ad: retired'),

# Publisher pages
('publisher:create', 'publisher: created'),
('publisher:update', 'publisher: updated'),
('publisher:delete', 'publisher: deleted'),
('publisher:view', 'publisher: viewed'),
('publisher:claimed', 'publisher: claimed'),

# Search
('search:basic_search', 'search: basic search run'),
)

activity_time = models.DateTimeField(auto_now_add=True)
activity_type = models.CharField(max_length=50, choices=ACTIVITY_TYPES)
content_type = models.ForeignKey(ContentType, blank=True, null=True)
object_id = models.PositiveIntegerField(blank=True, null=True)
object_model = GenericForeignKey('content_type', 'object_id')

@classmethod
def create(cls, app, action, object_model):
item_type = "{}:{}".format(app.lower(), action.lower())
if item_type not in dict(Activity.ACTIVITY_TYPES):
return None # XXX should we fail silently?
activity = cls(activity_type=item_type)
activity.object_model = object_model
activity.save()
return activity

class Meta:
ordering = ['-activity_time']
74 changes: 74 additions & 0 deletions activitystream/signals.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
from django.contrib.auth.signals import (
user_logged_in,
user_logged_out,
)
from django.db.models.signals import (
post_delete,
post_save,
)
from django.dispatch import receiver
from taggit.models import TaggedItem

from .models import Activity
from usermgmt.models import Profile


@receiver(post_save, sender=Profile)
def log_user_register(sender, **kwargs):
if kwargs['created']:
Activity.create('user', 'reg', kwargs['instance'].user)


@receiver(user_logged_in)
def log_login(sender, **kwargs):
Activity.create('user', 'login', kwargs['user'])


@receiver(user_logged_out)
def log_logout(sender, **kwargs):
Activity.create('user', 'logout', kwargs['user'])


@receiver(post_save)
def log_base_create_or_update(sender, **kwargs):
try:
name = {
'Flag': 'flag',
'Folder': 'folder',
'FriendGroup': 'group',
'Profile': 'profile',
'PublisherPage': 'publisher',
'Submission': 'submission',
'Tag': 'tag',
}[sender.__name__]
except:
return
if name == 'profile' and kwargs['created']:
return
Activity.create(
name,
'create' if kwargs['created'] else 'update',
kwargs['instance'])


@receiver(post_delete)
def log_base_delete(sender, **kwargs):
try:
name = {
'Flag': 'flag',
'Folder': 'folder',
'FriendGroup': 'group',
'Submission': 'submission',
'PublisherPage': 'publisher',
}[sender.__name__]
except:
return
Activity.create(name, 'delete', kwargs['instance'])


@receiver(post_save, sender=TaggedItem)
def log_tagged_item(sender, **kwargs):
Activity.create(
'tag',
'tag' if kwargs['created'] else 'update',
kwargs['instance'])