Skip to content

Commit db0e8ca

Browse files
committed
Initial commit
0 parents  commit db0e8ca

File tree

564 files changed

+27939
-0
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

564 files changed

+27939
-0
lines changed

.gitignore

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
#*
2+
*.pyc
3+
.DS_Store
4+
*.beam
5+
*~
6+
.bzr/
7+
*.elc
8+
*.swp
9+
*.db
10+
11+
settings_production.py

LICENCSE

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
GNU GENERAL PUBLIC LICENSE
2+
3+
Busylissy, Simple project management
4+
Copyright (C) 2010 Bread and Pepper
5+
6+
This program is free software: you can redistribute it and/or modify
7+
it under the terms of the GNU General Public License as published by
8+
the Free Software Foundation, either version 3 of the License, or
9+
(at your option) any later version.
10+
11+
This program is distributed in the hope that it will be useful,
12+
but WITHOUT ANY WARRANTY; without even the implied warranty of
13+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14+
GNU General Public License for more details.
15+
16+
You should have received a copy of the GNU General Public License
17+
along with this program. If not, see <http://www.gnu.org/licenses/>.

README.mkd

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
# BusyLissy
2+
3+
This is the software that is running the site
4+
[busylissy](http://busylissy.com). Busylissy started as a learning project for
5+
the us at [Bread and Pepper](http://breadandpepper). We also still use it to
6+
manage our own project.
7+
8+
Recently Busylissy was blogged about and reached a new high of 2.000 users.
9+
Because we are very busy workning on othher web applications we have decided to
10+
open source it.
11+
12+
# Credits
13+
- [Django](www.djangoproject.com) is the foundation on which Busylissy is
14+
build.
15+
- [django-swingtime](http://dakrauth.com/blog/entry/announcing-django-swingtime/) is
16+
used in BLAgenda.
17+
- [django-filebrowser](http://code.google.com/p/django-filebrowser/) is used in
18+
BLFile.
19+
20+
Then there is also django-tagging, django-registration, tagging, markdown,
21+
django-locale-url, sorl-thumbnail, django-treebeard.

__init__.py

Whitespace-only changes.

blactivity/__init__.py

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
from django.utils.translation import ugettext as _
2+
from django.contrib.contenttypes.models import ContentType
3+
4+
__all__ = ('register',)
5+
6+
class AlreadyRegistered(Exception):
7+
""" An attempt was made to register a model to activity more than once """
8+
pass
9+
10+
registry = []
11+
12+
def create_activity(self, actor, action, indirect_object=None):
13+
"""
14+
This method is added to the each model which is registered
15+
to Activities.
16+
17+
"""
18+
from busylizzy.blactivity.models import Activity
19+
20+
if self._meta.module_name == "thread":
21+
project = self.content_type.get_object_for_this_type(pk=self.object_id)
22+
elif self._meta.module_name == "message":
23+
project = self.thread.content_type.get_object_for_this_type(pk=self.thread.object_id)
24+
elif self._meta.module_name == "project":
25+
project = self
26+
else:
27+
project = self.project
28+
29+
activity = Activity(
30+
actor=actor,
31+
action=action,
32+
project=project,
33+
content_object=self,
34+
)
35+
36+
if indirect_object:
37+
activity.indirect_content_object = indirect_object
38+
39+
activity.save()
40+
41+
return activity
42+
43+
def register(model):
44+
""" Registers extra functionality to a model """
45+
if model in registry:
46+
raise AlreadyRegistered(
47+
_('The model %s has already been registered.') % model.__name__)
48+
registry.append(model)
49+
50+
# add method to model
51+
setattr(model, 'create_activity', create_activity)

blactivity/admin.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
from django.contrib import admin
2+
from busylizzy.blactivity.models import Activity
3+
4+
class ActivityAdmin(admin.ModelAdmin):
5+
pass
6+
7+
admin.site.register(Activity, ActivityAdmin)

blactivity/feeds.py

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
from django.contrib.syndication.feeds import Feed, FeedDoesNotExist
2+
from django.core.exceptions import ObjectDoesNotExist
3+
from django.contrib.auth.models import User
4+
from django.shortcuts import get_object_or_404
5+
from django.utils.hashcompat import md5_constructor
6+
7+
from busylizzy.blproject.models import Project
8+
from busylizzy.blactivity.models import Activity
9+
10+
class UserFeed(Feed):
11+
title = "Busylissy activity feed"
12+
link = "/projects/"
13+
copyright = 'Copyright (c) 2009, BusyLissy'
14+
15+
def get_object(self, bits):
16+
# In case of "/rss/beats/0613/foo/bar/baz/", or other such clutter,
17+
# check that bits has only one member.
18+
if len(bits) != 2:
19+
raise ObjectDoesNotExist
20+
else:
21+
user = get_object_or_404(User, username=bits[0])
22+
23+
salt = 'bltoken'
24+
hash_obj = md5_constructor(str(user.username) + salt + str(user.pk))
25+
if bits[1] != hash_obj.hexdigest():
26+
raise Object.DoesNotExist
27+
return user
28+
29+
def item_link(self, obj):
30+
return "/projects/%s/" % obj.project.slug
31+
32+
def item_pubdate(self, obj):
33+
return obj.time
34+
35+
def description(self, obj):
36+
return "Acitvity for busylissy"
37+
38+
def items(self, obj):
39+
projects = Project.objects.filter(members=obj)
40+
return Activity.objects.filter(project__in=projects)[:10]
41+
42+
class ProjectFeed(Feed):
43+
link = "/projects/"
44+
copyright = 'Copyright (c) 2009, BusyLissy'
45+
46+
def get_object(self, bits):
47+
# In case of "/rss/beats/0613/foo/bar/baz/", or other such clutter,
48+
# check that bits has only one member.
49+
if len(bits) != 2:
50+
raise ObjectDoesNotExist
51+
else:
52+
project = get_object_or_404(Project, slug=bits[0])
53+
54+
salt = 'bltoken'
55+
hash_obj = md5_constructor(str(project.slug) + salt + str(project.pk))
56+
57+
return project
58+
59+
def title(self, obj):
60+
return "%s activity feed" % obj.name
61+
62+
def item_link(self, obj):
63+
return "/projects/%s/" % obj.project.slug
64+
65+
def item_pubdate(self, obj):
66+
return obj.time
67+
68+
def description(self, obj):
69+
return "Activity for %s" % obj.name
70+
71+
def items(self, obj):
72+
return Activity.objects.filter(project=obj)[:10]

blactivity/models.py

Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
from django.db import models
2+
from django.conf import settings
3+
from django.utils.translation import ugettext as _
4+
from django.contrib.contenttypes.models import ContentType
5+
from django.contrib.contenttypes import generic
6+
from django.contrib.auth.models import User
7+
from django.template.loader import render_to_string
8+
from django.template import loader, Context
9+
from django.core.mail import send_mass_mail, EmailMessage
10+
11+
from busylizzy.blproject.models import Project
12+
13+
class ActivityManager(models.Manager):
14+
""" Manager for retrieving activities """
15+
def get_activities_for_project(self, project_slug, limit=None):
16+
""" Return the latest activities for a project """
17+
return self.filter(project__slug=project_slug)
18+
19+
class Activity(models.Model):
20+
""" Saving an activity """
21+
CREATE = 1
22+
UPDATE = 2
23+
DELETE = 3
24+
CLOSE = 4
25+
COMMENT = 5
26+
INVITE = 6
27+
START = 7
28+
ADMIN = 8
29+
JOIN = 9
30+
31+
ACTION_CHOICES = (
32+
(CREATE, _('created')),
33+
(UPDATE, _('updated')),
34+
(DELETE, _('deleted')),
35+
(CLOSE, _('closed')),
36+
(COMMENT, _('commented on')),
37+
(INVITE, _('invited')),
38+
(START, _('started')),
39+
(ADMIN, _('permissions')),
40+
(JOIN, _('joined')),
41+
)
42+
43+
actor = models.ForeignKey(User, related_name='activities')
44+
action = models.SmallIntegerField(_('action'), choices=ACTION_CHOICES)
45+
project = models.ForeignKey(Project, related_name='activities')
46+
time = models.DateTimeField(_('time'), auto_now_add=True)
47+
48+
# Direct object
49+
object_id = models.PositiveIntegerField()
50+
content_type = models.ForeignKey(ContentType, related_name='activity_objects')
51+
content_object = generic.GenericForeignKey('content_type', 'object_id')
52+
53+
# Indirect object
54+
indirect_object_id = models.PositiveIntegerField(blank=True, null=True)
55+
indirect_content_type = models.ForeignKey(ContentType, related_name='activity_indirect_objects', blank=True, null=True)
56+
indirect_content_object = generic.GenericForeignKey('indirect_content_type', 'indirect_object_id')
57+
58+
objects = ActivityManager()
59+
60+
def __unicode__(self):
61+
return self.humanize(admin=True).strip()
62+
63+
def save(self, *args, **kwargs):
64+
"""
65+
When saving an activity e-mail notification to project members
66+
"""
67+
super(Activity, self).save(*args, **kwargs)
68+
69+
# Send mail as notification
70+
if settings.EMAIL_NOTIFICATIONS is True:
71+
mail_data = self.prepare_mail()
72+
mail_data.send()
73+
74+
class Meta:
75+
verbose_name = _('activity')
76+
verbose_name_plural = _('activities')
77+
ordering = ['-time']
78+
79+
@property
80+
def app_label(self):
81+
"""
82+
Returns the ``app_label`` so we can search for the templates.
83+
84+
"""
85+
return self.content_type.app_label
86+
87+
def _get_template_for_action(self, mail=''):
88+
""" Returns the template for this action """
89+
options = ['%(app_label)s/actions/%(action)s%(mail)s.txt' % {'app_label': self.app_label,
90+
'action': self.get_action_display().replace(' ',''),
91+
'mail': mail,
92+
},
93+
'%(app_label)s/actions/generic%(mail)s.txt' % {'app_label': self.app_label,
94+
'mail': mail },
95+
'blactivity/generic.txt']
96+
t = loader.select_template(options)
97+
return t
98+
99+
def humanize(self, admin=False, mail=False):
100+
""" Returns a sentence of the committed action """
101+
if admin:
102+
template = loader.get_template('blactivity/admin.txt')
103+
elif mail:
104+
template = self._get_template_for_action('_mail')
105+
else:
106+
template = self._get_template_for_action()
107+
108+
context = Context(
109+
{'actor': self.actor.username,
110+
'actor_id': self.actor.id,
111+
'action': self.get_action_display(),
112+
'project': self.project,
113+
'project_id': self.project.id,
114+
'time': self.time,
115+
'object': self.content_object,
116+
'object_id': self.object_id,
117+
'object_type': self.content_type,
118+
'indirect_object': self.indirect_content_object if self.indirect_object_id else None,
119+
'indirect_object_id': self.indirect_object_id if self.indirect_object_id else None,
120+
'MEDIA_URL': settings.MEDIA_URL,
121+
})
122+
sentence = template.render(context)
123+
return sentence
124+
125+
def prepare_mail(self):
126+
""" Prepare mass mail """
127+
subject = "[%(project)s] New activity" % {'project': self.project }
128+
message = self.humanize(mail=True)
129+
from_email = settings.EMAIL_HOST_USER
130+
131+
email = EmailMessage(subject, message, from_email, self.recipient_list())
132+
email.content_subtype = "html"
133+
134+
return email
135+
136+
def recipient_list(self):
137+
""" Return list with all recipient for mail """
138+
recipient_list = []
139+
for member in self.project.members.all():
140+
try:
141+
profile = member.get_profile()
142+
except:
143+
pass
144+
else:
145+
if profile.notifications:
146+
recipient_list.append(member.email)
147+
148+
if len(recipient_list) > 0:
149+
return recipient_list
150+
else: return None
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{% load i18n time %}
2+
{{ actor }} {{ action }} {% if indirect_object %}{{ indirect_object }}{% else %}{% if object %}{{ object }}{% else %}{{ object_type }}{% endif %}{% endif %} {% humanize_timesince time %} ago.
3+
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<p>Project: {{ project }}</p>
2+
<p>
3+
You can always check the Overview for the latest project activity:<br />
4+
<a href="http://busylissy.com{% url project-detail project.slug %}">http://busylissy.com{% url project-detail project.slug %}</a>
5+
</p>
6+
<p>
7+
To stop receiving notifications on updates of {{ project }}, visit:<br />
8+
<a href="http://busylissy.com{% url profile-settings %}">http://busylissy.com{% url profile-settings %}</a>
9+
</p>

0 commit comments

Comments
 (0)