Merge branch 'master' into permissions

Conflicts:
	greenmine/scrum/api.py
	greenmine/scrum/models.py
remotes/origin/enhancement/email-actions
Jesús Espino 2013-04-01 09:52:17 +02:00
commit 17039f9780
12 changed files with 124 additions and 49 deletions

5
.ctags Normal file
View File

@ -0,0 +1,5 @@
--languages=python
--exclude=".git"
--exclude="tags"
--exclude="*.pyc"
-R

View File

@ -27,6 +27,7 @@ class ApiRoot(APIView):
'change-attachments': reverse('change-attachment-list', request=request, format=format), 'change-attachments': reverse('change-attachment-list', request=request, format=format),
'issues': reverse('issue-list', request=request, format=format), 'issues': reverse('issue-list', request=request, format=format),
'tasks': reverse('task-list', request=request, format=format), 'tasks': reverse('task-list', request=request, format=format),
'issues': reverse('issue-list', request=request, format=format),
'severities': reverse('severity-list', request=request, format=format), 'severities': reverse('severity-list', request=request, format=format),
'issue-status': reverse('issue-status-list', request=request, format=format), 'issue-status': reverse('issue-status-list', request=request, format=format),
'task-status': reverse('task-status-list', request=request, format=format), 'task-status': reverse('task-status-list', request=request, format=format),

24
greenmine/base/auth.py Normal file
View File

@ -0,0 +1,24 @@
# -*- coding: utf-8 -*-
from rest_framework.authentication import BaseAuthentication
class SessionAuthentication(BaseAuthentication):
"""
Use Django's session framework for authentication without csrf.
"""
def authenticate(self, request):
"""
Returns a `User` if the request session currently has a logged in user.
Otherwise returns `None`.
"""
http_request = request._request
user = getattr(http_request, 'user', None)
if not user or not user.is_active:
return None
return (user, None)

View File

@ -45,7 +45,8 @@ admin.site.register(models.Milestone, MilestoneAdmin)
class UserStoryAdmin(reversion.VersionAdmin): class UserStoryAdmin(reversion.VersionAdmin):
list_display = ["ref", "milestone", "project", "owner", 'status', 'is_closed'] list_display = ["id", "ref", "milestone", "project", "owner", 'status', 'is_closed']
list_filter = ["milestone", "project"]
admin.site.register(models.UserStory, UserStoryAdmin) admin.site.register(models.UserStory, UserStoryAdmin)
@ -63,7 +64,11 @@ admin.site.register(models.ChangeAttachment, ChangeAttachmentAdmin)
class TaskAdmin(reversion.VersionAdmin): class TaskAdmin(reversion.VersionAdmin):
list_display = ["subject", "user_story"] list_display = ["subject", "user_story", "milestone", "project", "user_story_id"]
list_filter = ["user_story", "milestone", "project"]
def user_story_id(self, instance):
return instance.user_story.id
class IssueAdmin(reversion.VersionAdmin): class IssueAdmin(reversion.VersionAdmin):

View File

@ -39,6 +39,8 @@ class ProjectList(generics.ListCreateAPIView):
def get_queryset(self): def get_queryset(self):
return self.model.objects.filter(members=self.request.user) return self.model.objects.filter(members=self.request.user)
def pre_save(self, obj):
obj.owner = self.request.user
class ProjectDetail(generics.RetrieveUpdateDestroyAPIView): class ProjectDetail(generics.RetrieveUpdateDestroyAPIView):
model = Project model = Project
@ -54,6 +56,9 @@ class MilestoneList(generics.ListCreateAPIView):
def get_queryset(self): def get_queryset(self):
return self.model.objects.filter(project__members=self.request.user) return self.model.objects.filter(project__members=self.request.user)
def pre_save(self, obj):
obj.owner = self.request.user
class MilestoneDetail(generics.RetrieveUpdateDestroyAPIView): class MilestoneDetail(generics.RetrieveUpdateDestroyAPIView):
model = Milestone model = Milestone
@ -69,6 +74,9 @@ class UserStoryList(generics.ListCreateAPIView):
def get_queryset(self): def get_queryset(self):
return self.model.objects.filter(project__members=self.request.user) return self.model.objects.filter(project__members=self.request.user)
def pre_save(self, obj):
obj.owner = self.request.user
class UserStoryDetail(generics.RetrieveUpdateDestroyAPIView): class UserStoryDetail(generics.RetrieveUpdateDestroyAPIView):
model = UserStory model = UserStory
@ -83,6 +91,9 @@ class ChangeList(generics.ListCreateAPIView):
def get_queryset(self): def get_queryset(self):
return self.model.objects.filter(project__members=self.request.user) return self.model.objects.filter(project__members=self.request.user)
def pre_save(self, obj):
obj.owner = self.request.user
class ChangeDetail(generics.RetrieveUpdateDestroyAPIView): class ChangeDetail(generics.RetrieveUpdateDestroyAPIView):
model = Change model = Change
@ -97,6 +108,9 @@ class ChangeAttachmentList(generics.ListCreateAPIView):
def get_queryset(self): def get_queryset(self):
return self.model.objects.filter(change__project__members=self.request.user) return self.model.objects.filter(change__project__members=self.request.user)
def pre_save(self, obj):
obj.owner = self.request.user
class ChangeAttachmentDetail(generics.RetrieveUpdateDestroyAPIView): class ChangeAttachmentDetail(generics.RetrieveUpdateDestroyAPIView):
model = ChangeAttachment model = ChangeAttachment
@ -127,6 +141,9 @@ class TaskList(generics.ListCreateAPIView):
def get_queryset(self): def get_queryset(self):
return self.model.objects.filter(project__members=self.request.user) return self.model.objects.filter(project__members=self.request.user)
def pre_save(self, obj):
obj.owner = self.request.user
class TaskDetail(generics.RetrieveUpdateDestroyAPIView): class TaskDetail(generics.RetrieveUpdateDestroyAPIView):
model = Task model = Task
@ -134,6 +151,20 @@ class TaskDetail(generics.RetrieveUpdateDestroyAPIView):
permission_classes = (TaskDetailPermission,) permission_classes = (TaskDetailPermission,)
class IssueList(generics.ListCreateAPIView):
model = Issue
serializer_class = IssueSerializer
#filter_fields = ('project')
def pre_save(self, obj):
obj.owner = self.request.user
class IssueDetail(generics.RetrieveUpdateDestroyAPIView):
model = Issue
serializer_class = IssueSerializer
class SeverityList(generics.ListCreateAPIView): class SeverityList(generics.ListCreateAPIView):
model = Severity model = Severity
serializer_class = SeveritySerializer serializer_class = SeveritySerializer

View File

@ -133,7 +133,7 @@ class Command(BaseCommand):
owner=project.owner, owner=project.owner,
severity=Severity.objects.get(project=project, order=2), severity=Severity.objects.get(project=project, order=2),
status=IssueStatus.objects.get(project=project, order=4), status=IssueStatus.objects.get(project=project, order=4),
priority=Priority.objects.get(project=project, order=2), priority=Priority.objects.get(project=project, order=3),
type=IssueType.objects.get(project=project, order=1), type=IssueType.objects.get(project=project, order=1),
tags=[], tags=[],
) )

View File

@ -124,7 +124,7 @@ class Project(models.Model):
created_date = models.DateTimeField(auto_now_add=True) created_date = models.DateTimeField(auto_now_add=True)
modified_date = models.DateTimeField(auto_now_add=True, auto_now=True) modified_date = models.DateTimeField(auto_now_add=True, auto_now=True)
owner = models.ForeignKey("base.User", related_name="owned_projects") owner = models.ForeignKey("base.User", related_name="owned_projects", blank=True)
members = models.ManyToManyField("base.User", related_name="projects", through='Membership') members = models.ManyToManyField("base.User", related_name="projects", through='Membership')
public = models.BooleanField(default=True) public = models.BooleanField(default=True)
@ -160,7 +160,8 @@ class Milestone(models.Model):
uuid = models.CharField(max_length=40, unique=True, blank=True) uuid = models.CharField(max_length=40, unique=True, blank=True)
name = models.CharField(max_length=200, db_index=True) name = models.CharField(max_length=200, db_index=True)
slug = models.SlugField(max_length=250, unique=True, blank=True) slug = models.SlugField(max_length=250, unique=True, blank=True)
owner = models.ForeignKey('base.User', related_name="milestones") owner = models.ForeignKey('base.User', related_name="milestones",
null=True, blank=True)
project = models.ForeignKey('Project', related_name="milestones") project = models.ForeignKey('Project', related_name="milestones")
estimated_start = models.DateField(null=True, default=None) estimated_start = models.DateField(null=True, default=None)
@ -173,8 +174,6 @@ class Milestone(models.Model):
disponibility = models.FloatField(null=True, default=0.0) disponibility = models.FloatField(null=True, default=0.0)
order = models.PositiveSmallIntegerField("Order", default=1) order = models.PositiveSmallIntegerField("Order", default=1)
tags = PickledObjectField()
def save(self, *args, **kwargs): def save(self, *args, **kwargs):
if not self.slug: if not self.slug:
self.slug = slugify_uniquely(self.name, self.__class__) self.slug = slugify_uniquely(self.name, self.__class__)
@ -188,15 +187,6 @@ class Milestone(models.Model):
('can_view_milestone', 'Can view milestones'), ('can_view_milestone', 'Can view milestones'),
) )
@property
def total_points(self):
"""
Get total story points for this milestone.
"""
total = sum(iter_points(self.user_stories.all()))
return "{0:.1f}".format(total)
def __unicode__(self): def __unicode__(self):
return self.name return self.name
@ -206,12 +196,12 @@ class Milestone(models.Model):
class UserStory(models.Model): class UserStory(models.Model):
uuid = models.CharField(max_length=40, unique=True, blank=True) uuid = models.CharField(max_length=40, unique=True, blank=True)
ref = models.BigIntegerField(db_index=True, null=True, default=None) ref = models.BigIntegerField(db_index=True, null=True, default=None, blank=True)
milestone = models.ForeignKey("Milestone", blank=True, milestone = models.ForeignKey("Milestone", blank=True,
related_name="user_stories", null=True, related_name="user_stories", null=True,
default=None) default=None)
project = models.ForeignKey("Project", related_name="user_stories") project = models.ForeignKey("Project", related_name="user_stories")
owner = models.ForeignKey("base.User", null=True, default=None, owner = models.ForeignKey("base.User", blank=True, null=True,
related_name="user_stories") related_name="user_stories")
status = models.ForeignKey("UserStoryStatus", related_name="userstories") status = models.ForeignKey("UserStoryStatus", related_name="userstories")
@ -251,12 +241,6 @@ class UserStory(models.Model):
def is_closed(self): def is_closed(self):
return self.status.is_closed return self.status.is_closed
def save(self, *args, **kwargs):
if self.ref is None and self.project:
self.ref = ref_uniquely(self.project, "last_us_ref", self.__class__)
super(UserStory, self).save(*args, **kwargs)
class Change(models.Model): class Change(models.Model):
change_type = models.IntegerField(choices=TASK_CHANGE_CHOICES) change_type = models.IntegerField(choices=TASK_CHANGE_CHOICES)
@ -398,8 +382,14 @@ class Issue(models.Model):
# Model related signals handlers # Model related signals handlers
@receiver(models.signals.post_save, sender=Project, dispatch_uid="project_post_save") @receiver(models.signals.post_save, sender=Project, dispatch_uid="project_post_save")
def project_post_save(sender, instance, created, **kwargs): def project_post_save(sender, instance, created, **kwargs):
"""
Create all project model depences on project is
created.
"""
if not created: if not created:
return return
@ -416,7 +406,7 @@ def project_post_save(sender, instance, created, **kwargs):
UserStoryStatus.objects.create(name=name, order=order, UserStoryStatus.objects.create(name=name, order=order,
is_closed=is_closed, project=instance) is_closed=is_closed, project=instance)
for order, name in POINTS_CHOICES: for order, name in PRIORITY_CHOICES:
Priority.objects.create(project=instance, name=name, order=order) Priority.objects.create(project=instance, name=name, order=order)
for order, name in SEVERITY_CHOICES: for order, name in SEVERITY_CHOICES:
@ -429,6 +419,17 @@ def project_post_save(sender, instance, created, **kwargs):
IssueType.objects.create(project=instance, name=name, order=order) IssueType.objects.create(project=instance, name=name, order=order)
@receiver(models.signals.pre_save, sender=UserStory, dispatch_uid="user_story_ref_handler")
def user_story_ref_handler(sender, instance, **kwargs):
"""
Automatically assignes a seguent reference code to a
user story if that is not created.
"""
if not instance.id and instance.project:
instance.ref = ref_uniquely(instance.project, "last_us_ref", instance.__class__)
# Email alerts signals handlers # Email alerts signals handlers
# TODO: temporary commented (Pending refactor) # TODO: temporary commented (Pending refactor)
# from . import sigdispatch # from . import sigdispatch

View File

@ -16,6 +16,12 @@ class PickleField(serializers.WritableField):
return data return data
class PointsSerializer(serializers.ModelSerializer):
class Meta:
model = Points
fields = ()
class ProjectSerializer(serializers.ModelSerializer): class ProjectSerializer(serializers.ModelSerializer):
tags = PickleField() tags = PickleField()
@ -26,7 +32,7 @@ class ProjectSerializer(serializers.ModelSerializer):
class UserStorySerializer(serializers.ModelSerializer): class UserStorySerializer(serializers.ModelSerializer):
tags = PickleField() tags = PickleField()
is_closed = serializers.BooleanField() is_closed = serializers.Field(source='is_closed')
class Meta: class Meta:
model = UserStory model = UserStory
@ -35,8 +41,7 @@ class UserStorySerializer(serializers.ModelSerializer):
class MilestoneSerializer(serializers.ModelSerializer): class MilestoneSerializer(serializers.ModelSerializer):
tags = PickleField() user_stories = UserStorySerializer(many=True, required=False)
user_stories = UserStorySerializer(many=True)
class Meta: class Meta:
model = Milestone model = Milestone
@ -75,6 +80,14 @@ class IssueSerializer(serializers.ModelSerializer):
fields = () fields = ()
class IssueSerializer(serializers.ModelSerializer):
tags = PickleField()
class Meta:
model = Issue
fields = ()
class SeveritySerializer(serializers.ModelSerializer): class SeveritySerializer(serializers.ModelSerializer):
class Meta: class Meta:
model = Severity model = Severity
@ -111,7 +124,3 @@ class IssueTypeSerializer(serializers.ModelSerializer):
fields = () fields = ()
class PointsSerializer(serializers.ModelSerializer):
class Meta:
model = Points
fields = ()

View File

@ -18,6 +18,8 @@ urlpatterns = format_suffix_patterns(patterns('',
url(r'^issues/(?P<pk>[0-9]+)/$', api.IssueDetail.as_view(), name='issue-detail'), url(r'^issues/(?P<pk>[0-9]+)/$', api.IssueDetail.as_view(), name='issue-detail'),
url(r'^tasks/$', api.TaskList.as_view(), name='task-list'), url(r'^tasks/$', api.TaskList.as_view(), name='task-list'),
url(r'^tasks/(?P<pk>[0-9]+)/$', api.TaskDetail.as_view(), name='task-detail'), url(r'^tasks/(?P<pk>[0-9]+)/$', api.TaskDetail.as_view(), name='task-detail'),
url(r'^issues/$', api.IssueList.as_view(), name='issue-list'),
url(r'^issues/(?P<pk>[0-9]+)/$', api.IssueDetail.as_view(), name='issue-detail'),
url(r'^severities/$', api.SeverityList.as_view(), name='severity-list'), url(r'^severities/$', api.SeverityList.as_view(), name='severity-list'),
url(r'^severities/(?P<pk>[0-9]+)/$', api.SeverityDetail.as_view(), name='severity-detail'), url(r'^severities/(?P<pk>[0-9]+)/$', api.SeverityDetail.as_view(), name='severity-detail'),
url(r'^issue_status/$', api.IssueStatusList.as_view(), name='issue-status-list'), url(r'^issue_status/$', api.IssueStatusList.as_view(), name='issue-status-list'),

View File

@ -3,15 +3,9 @@
from __future__ import absolute_import from __future__ import absolute_import
import os import os
if "GREENMINE_ENVIRON" in os.environ: try:
if os.environ["GREENMINE_ENVIRON"] in ('production', 'development', 'local'): print "Trying import local.py settings..."
print "importing %s" % os.environ["GREENMINE_ENVIRON"] from .local import *
eval("from .%s import *" % (os.environ["GREENMINE_ENVIRON"])) except ImportError:
print "Trying import development.py settings..."
else: from .development import *
try:
print "Trying import local.py settings..."
from .local import *
except ImportError:
print "Trying import development.py settings..."
from .development import *

View File

@ -163,13 +163,13 @@ TEMPLATE_LOADERS = [
MIDDLEWARE_CLASSES = [ MIDDLEWARE_CLASSES = [
'django.middleware.common.CommonMiddleware', 'django.middleware.common.CommonMiddleware',
'django.middleware.locale.LocaleMiddleware',
'greenmine.base.middleware.GreenmineSessionMiddleware', 'greenmine.base.middleware.GreenmineSessionMiddleware',
'greenmine.base.middleware.CoorsMiddleware', 'greenmine.base.middleware.CoorsMiddleware',
'django.middleware.locale.LocaleMiddleware', #'django.middleware.csrf.CsrfViewMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware', #'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware', #'django.middleware.clickjacking.XFrameOptionsMiddleware',
'django.middleware.transaction.TransactionMiddleware', 'django.middleware.transaction.TransactionMiddleware',
'reversion.middleware.RevisionMiddleware', 'reversion.middleware.RevisionMiddleware',
] ]
@ -319,7 +319,7 @@ HAYSTACK_DEFAULT_OPERATOR = 'AND'
REST_FRAMEWORK = { REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': ( 'DEFAULT_AUTHENTICATION_CLASSES': (
'rest_framework.authentication.SessionAuthentication', 'greenmine.base.auth.SessionAuthentication',
), ),
'FILTER_BACKEND': 'rest_framework.filters.DjangoFilterBackend', 'FILTER_BACKEND': 'rest_framework.filters.DjangoFilterBackend',
} }

View File

@ -1,4 +1,6 @@
# -*- coding: utf-8 -*-
from django.conf.urls import patterns, include, url from django.conf.urls import patterns, include, url
from django.contrib.staticfiles.urls import staticfiles_urlpatterns
from django.contrib import admin from django.contrib import admin
admin.autodiscover() admin.autodiscover()
@ -13,3 +15,4 @@ urlpatterns = patterns('',
url(r'^grappelli/', include('grappelli.urls')), url(r'^grappelli/', include('grappelli.urls')),
) )
urlpatterns += staticfiles_urlpatterns()