Merge branch 'master' into permissions
Conflicts: greenmine/scrum/api.py greenmine/scrum/models.pyremotes/origin/enhancement/email-actions
commit
17039f9780
|
@ -0,0 +1,5 @@
|
|||
--languages=python
|
||||
--exclude=".git"
|
||||
--exclude="tags"
|
||||
--exclude="*.pyc"
|
||||
-R
|
|
@ -27,6 +27,7 @@ class ApiRoot(APIView):
|
|||
'change-attachments': reverse('change-attachment-list', request=request, format=format),
|
||||
'issues': reverse('issue-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),
|
||||
'issue-status': reverse('issue-status-list', request=request, format=format),
|
||||
'task-status': reverse('task-status-list', request=request, format=format),
|
||||
|
|
|
@ -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)
|
||||
|
|
@ -45,7 +45,8 @@ admin.site.register(models.Milestone, MilestoneAdmin)
|
|||
|
||||
|
||||
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)
|
||||
|
||||
|
@ -63,7 +64,11 @@ admin.site.register(models.ChangeAttachment, ChangeAttachmentAdmin)
|
|||
|
||||
|
||||
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):
|
||||
|
|
|
@ -39,6 +39,8 @@ class ProjectList(generics.ListCreateAPIView):
|
|||
def get_queryset(self):
|
||||
return self.model.objects.filter(members=self.request.user)
|
||||
|
||||
def pre_save(self, obj):
|
||||
obj.owner = self.request.user
|
||||
|
||||
class ProjectDetail(generics.RetrieveUpdateDestroyAPIView):
|
||||
model = Project
|
||||
|
@ -54,6 +56,9 @@ class MilestoneList(generics.ListCreateAPIView):
|
|||
def get_queryset(self):
|
||||
return self.model.objects.filter(project__members=self.request.user)
|
||||
|
||||
def pre_save(self, obj):
|
||||
obj.owner = self.request.user
|
||||
|
||||
|
||||
class MilestoneDetail(generics.RetrieveUpdateDestroyAPIView):
|
||||
model = Milestone
|
||||
|
@ -69,6 +74,9 @@ class UserStoryList(generics.ListCreateAPIView):
|
|||
def get_queryset(self):
|
||||
return self.model.objects.filter(project__members=self.request.user)
|
||||
|
||||
def pre_save(self, obj):
|
||||
obj.owner = self.request.user
|
||||
|
||||
|
||||
class UserStoryDetail(generics.RetrieveUpdateDestroyAPIView):
|
||||
model = UserStory
|
||||
|
@ -83,6 +91,9 @@ class ChangeList(generics.ListCreateAPIView):
|
|||
def get_queryset(self):
|
||||
return self.model.objects.filter(project__members=self.request.user)
|
||||
|
||||
def pre_save(self, obj):
|
||||
obj.owner = self.request.user
|
||||
|
||||
|
||||
class ChangeDetail(generics.RetrieveUpdateDestroyAPIView):
|
||||
model = Change
|
||||
|
@ -97,6 +108,9 @@ class ChangeAttachmentList(generics.ListCreateAPIView):
|
|||
def get_queryset(self):
|
||||
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):
|
||||
model = ChangeAttachment
|
||||
|
@ -127,6 +141,9 @@ class TaskList(generics.ListCreateAPIView):
|
|||
def get_queryset(self):
|
||||
return self.model.objects.filter(project__members=self.request.user)
|
||||
|
||||
def pre_save(self, obj):
|
||||
obj.owner = self.request.user
|
||||
|
||||
|
||||
class TaskDetail(generics.RetrieveUpdateDestroyAPIView):
|
||||
model = Task
|
||||
|
@ -134,6 +151,20 @@ class TaskDetail(generics.RetrieveUpdateDestroyAPIView):
|
|||
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):
|
||||
model = Severity
|
||||
serializer_class = SeveritySerializer
|
||||
|
|
|
@ -133,7 +133,7 @@ class Command(BaseCommand):
|
|||
owner=project.owner,
|
||||
severity=Severity.objects.get(project=project, order=2),
|
||||
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),
|
||||
tags=[],
|
||||
)
|
||||
|
|
|
@ -124,7 +124,7 @@ class Project(models.Model):
|
|||
created_date = models.DateTimeField(auto_now_add=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')
|
||||
public = models.BooleanField(default=True)
|
||||
|
||||
|
@ -160,7 +160,8 @@ class Milestone(models.Model):
|
|||
uuid = models.CharField(max_length=40, unique=True, blank=True)
|
||||
name = models.CharField(max_length=200, db_index=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")
|
||||
|
||||
estimated_start = models.DateField(null=True, default=None)
|
||||
|
@ -173,8 +174,6 @@ class Milestone(models.Model):
|
|||
disponibility = models.FloatField(null=True, default=0.0)
|
||||
order = models.PositiveSmallIntegerField("Order", default=1)
|
||||
|
||||
tags = PickledObjectField()
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
if not self.slug:
|
||||
self.slug = slugify_uniquely(self.name, self.__class__)
|
||||
|
@ -188,15 +187,6 @@ class Milestone(models.Model):
|
|||
('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):
|
||||
return self.name
|
||||
|
||||
|
@ -206,12 +196,12 @@ class Milestone(models.Model):
|
|||
|
||||
class UserStory(models.Model):
|
||||
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,
|
||||
related_name="user_stories", null=True,
|
||||
default=None)
|
||||
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")
|
||||
|
||||
status = models.ForeignKey("UserStoryStatus", related_name="userstories")
|
||||
|
@ -251,12 +241,6 @@ class UserStory(models.Model):
|
|||
def is_closed(self):
|
||||
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):
|
||||
change_type = models.IntegerField(choices=TASK_CHANGE_CHOICES)
|
||||
|
@ -398,8 +382,14 @@ class Issue(models.Model):
|
|||
|
||||
|
||||
# Model related signals handlers
|
||||
|
||||
@receiver(models.signals.post_save, sender=Project, dispatch_uid="project_post_save")
|
||||
def project_post_save(sender, instance, created, **kwargs):
|
||||
"""
|
||||
Create all project model depences on project is
|
||||
created.
|
||||
"""
|
||||
|
||||
if not created:
|
||||
return
|
||||
|
||||
|
@ -416,7 +406,7 @@ def project_post_save(sender, instance, created, **kwargs):
|
|||
UserStoryStatus.objects.create(name=name, order=order,
|
||||
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)
|
||||
|
||||
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)
|
||||
|
||||
|
||||
@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
|
||||
# TODO: temporary commented (Pending refactor)
|
||||
# from . import sigdispatch
|
||||
|
|
|
@ -16,6 +16,12 @@ class PickleField(serializers.WritableField):
|
|||
return data
|
||||
|
||||
|
||||
class PointsSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = Points
|
||||
fields = ()
|
||||
|
||||
|
||||
class ProjectSerializer(serializers.ModelSerializer):
|
||||
tags = PickleField()
|
||||
|
||||
|
@ -26,7 +32,7 @@ class ProjectSerializer(serializers.ModelSerializer):
|
|||
|
||||
class UserStorySerializer(serializers.ModelSerializer):
|
||||
tags = PickleField()
|
||||
is_closed = serializers.BooleanField()
|
||||
is_closed = serializers.Field(source='is_closed')
|
||||
|
||||
class Meta:
|
||||
model = UserStory
|
||||
|
@ -35,8 +41,7 @@ class UserStorySerializer(serializers.ModelSerializer):
|
|||
|
||||
|
||||
class MilestoneSerializer(serializers.ModelSerializer):
|
||||
tags = PickleField()
|
||||
user_stories = UserStorySerializer(many=True)
|
||||
user_stories = UserStorySerializer(many=True, required=False)
|
||||
|
||||
class Meta:
|
||||
model = Milestone
|
||||
|
@ -75,6 +80,14 @@ class IssueSerializer(serializers.ModelSerializer):
|
|||
fields = ()
|
||||
|
||||
|
||||
class IssueSerializer(serializers.ModelSerializer):
|
||||
tags = PickleField()
|
||||
|
||||
class Meta:
|
||||
model = Issue
|
||||
fields = ()
|
||||
|
||||
|
||||
class SeveritySerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = Severity
|
||||
|
@ -111,7 +124,3 @@ class IssueTypeSerializer(serializers.ModelSerializer):
|
|||
fields = ()
|
||||
|
||||
|
||||
class PointsSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = Points
|
||||
fields = ()
|
||||
|
|
|
@ -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'^tasks/$', api.TaskList.as_view(), name='task-list'),
|
||||
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/(?P<pk>[0-9]+)/$', api.SeverityDetail.as_view(), name='severity-detail'),
|
||||
url(r'^issue_status/$', api.IssueStatusList.as_view(), name='issue-status-list'),
|
||||
|
|
|
@ -3,15 +3,9 @@
|
|||
from __future__ import absolute_import
|
||||
import os
|
||||
|
||||
if "GREENMINE_ENVIRON" in os.environ:
|
||||
if os.environ["GREENMINE_ENVIRON"] in ('production', 'development', 'local'):
|
||||
print "importing %s" % os.environ["GREENMINE_ENVIRON"]
|
||||
eval("from .%s import *" % (os.environ["GREENMINE_ENVIRON"]))
|
||||
|
||||
else:
|
||||
try:
|
||||
print "Trying import local.py settings..."
|
||||
from .local import *
|
||||
except ImportError:
|
||||
print "Trying import development.py settings..."
|
||||
from .development import *
|
||||
try:
|
||||
print "Trying import local.py settings..."
|
||||
from .local import *
|
||||
except ImportError:
|
||||
print "Trying import development.py settings..."
|
||||
from .development import *
|
||||
|
|
|
@ -163,13 +163,13 @@ TEMPLATE_LOADERS = [
|
|||
|
||||
MIDDLEWARE_CLASSES = [
|
||||
'django.middleware.common.CommonMiddleware',
|
||||
'django.middleware.locale.LocaleMiddleware',
|
||||
'greenmine.base.middleware.GreenmineSessionMiddleware',
|
||||
'greenmine.base.middleware.CoorsMiddleware',
|
||||
'django.middleware.locale.LocaleMiddleware',
|
||||
'django.middleware.csrf.CsrfViewMiddleware',
|
||||
#'django.middleware.csrf.CsrfViewMiddleware',
|
||||
'django.contrib.auth.middleware.AuthenticationMiddleware',
|
||||
'django.contrib.messages.middleware.MessageMiddleware',
|
||||
'django.middleware.clickjacking.XFrameOptionsMiddleware',
|
||||
#'django.contrib.messages.middleware.MessageMiddleware',
|
||||
#'django.middleware.clickjacking.XFrameOptionsMiddleware',
|
||||
'django.middleware.transaction.TransactionMiddleware',
|
||||
'reversion.middleware.RevisionMiddleware',
|
||||
]
|
||||
|
@ -319,7 +319,7 @@ HAYSTACK_DEFAULT_OPERATOR = 'AND'
|
|||
|
||||
REST_FRAMEWORK = {
|
||||
'DEFAULT_AUTHENTICATION_CLASSES': (
|
||||
'rest_framework.authentication.SessionAuthentication',
|
||||
'greenmine.base.auth.SessionAuthentication',
|
||||
),
|
||||
'FILTER_BACKEND': 'rest_framework.filters.DjangoFilterBackend',
|
||||
}
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from django.conf.urls import patterns, include, url
|
||||
from django.contrib.staticfiles.urls import staticfiles_urlpatterns
|
||||
|
||||
from django.contrib import admin
|
||||
admin.autodiscover()
|
||||
|
@ -13,3 +15,4 @@ urlpatterns = patterns('',
|
|||
url(r'^grappelli/', include('grappelli.urls')),
|
||||
)
|
||||
|
||||
urlpatterns += staticfiles_urlpatterns()
|
||||
|
|
Loading…
Reference in New Issue