diff --git a/.ctags b/.ctags new file mode 100644 index 00000000..ab23a05e --- /dev/null +++ b/.ctags @@ -0,0 +1,5 @@ +--languages=python +--exclude=".git" +--exclude="tags" +--exclude="*.pyc" +-R diff --git a/greenmine/base/api.py b/greenmine/base/api.py index 3ea1fe6a..289878cc 100644 --- a/greenmine/base/api.py +++ b/greenmine/base/api.py @@ -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), diff --git a/greenmine/base/auth.py b/greenmine/base/auth.py new file mode 100644 index 00000000..6ca71405 --- /dev/null +++ b/greenmine/base/auth.py @@ -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) + diff --git a/greenmine/scrum/admin.py b/greenmine/scrum/admin.py index e8bf37bc..0db41896 100644 --- a/greenmine/scrum/admin.py +++ b/greenmine/scrum/admin.py @@ -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): diff --git a/greenmine/scrum/api.py b/greenmine/scrum/api.py index a4eb8c12..358e3c79 100644 --- a/greenmine/scrum/api.py +++ b/greenmine/scrum/api.py @@ -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 diff --git a/greenmine/scrum/management/commands/sample_data.py b/greenmine/scrum/management/commands/sample_data.py index 650b94ad..bae8a828 100644 --- a/greenmine/scrum/management/commands/sample_data.py +++ b/greenmine/scrum/management/commands/sample_data.py @@ -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=[], ) diff --git a/greenmine/scrum/models.py b/greenmine/scrum/models.py index c8a74785..c1c6ba0a 100644 --- a/greenmine/scrum/models.py +++ b/greenmine/scrum/models.py @@ -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 diff --git a/greenmine/scrum/serializers.py b/greenmine/scrum/serializers.py index 691de5ab..95008644 100644 --- a/greenmine/scrum/serializers.py +++ b/greenmine/scrum/serializers.py @@ -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 = () diff --git a/greenmine/scrum/urls.py b/greenmine/scrum/urls.py index 732fdebf..e96b5f83 100644 --- a/greenmine/scrum/urls.py +++ b/greenmine/scrum/urls.py @@ -18,6 +18,8 @@ urlpatterns = format_suffix_patterns(patterns('', url(r'^issues/(?P[0-9]+)/$', api.IssueDetail.as_view(), name='issue-detail'), url(r'^tasks/$', api.TaskList.as_view(), name='task-list'), url(r'^tasks/(?P[0-9]+)/$', api.TaskDetail.as_view(), name='task-detail'), + url(r'^issues/$', api.IssueList.as_view(), name='issue-list'), + url(r'^issues/(?P[0-9]+)/$', api.IssueDetail.as_view(), name='issue-detail'), url(r'^severities/$', api.SeverityList.as_view(), name='severity-list'), url(r'^severities/(?P[0-9]+)/$', api.SeverityDetail.as_view(), name='severity-detail'), url(r'^issue_status/$', api.IssueStatusList.as_view(), name='issue-status-list'), diff --git a/greenmine/settings/__init__.py b/greenmine/settings/__init__.py index 2201e479..da0350a5 100644 --- a/greenmine/settings/__init__.py +++ b/greenmine/settings/__init__.py @@ -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 * diff --git a/greenmine/settings/common.py b/greenmine/settings/common.py index 6b3df119..fc205e79 100644 --- a/greenmine/settings/common.py +++ b/greenmine/settings/common.py @@ -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', } diff --git a/greenmine/urls.py b/greenmine/urls.py index 4136a7e7..cdf74ee0 100644 --- a/greenmine/urls.py +++ b/greenmine/urls.py @@ -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()