Merge branch 'master' into notifications
Conflicts: greenmine/base/models.py greenmine/scrum/models.pyremotes/origin/enhancement/email-actions
commit
59b9e65fe5
|
@ -1,5 +1,6 @@
|
|||
.*.sw*
|
||||
*.log
|
||||
greenmine/search
|
||||
greenmine/settings/local.py
|
||||
database.sqlite
|
||||
logs
|
||||
|
|
|
@ -13,9 +13,13 @@ from rest_framework.permissions import IsAuthenticated
|
|||
from rest_framework import status
|
||||
from rest_framework import generics
|
||||
|
||||
from haystack.query import SearchQuerySet
|
||||
|
||||
from greenmine.base.serializers import LoginSerializer, UserLogged, UserSerializer, RoleSerializer
|
||||
from greenmine.base.serializers import SearchSerializer
|
||||
from greenmine.base.models import User, Role
|
||||
from greenmine.scrum import models
|
||||
from django.conf import settings
|
||||
|
||||
import django_filters
|
||||
|
||||
|
@ -36,14 +40,15 @@ class ApiRoot(APIView):
|
|||
'issues': reverse('issues-list', request=request, format=format),
|
||||
'tasks': reverse('tasks-list', request=request, format=format),
|
||||
'tasks/statuses': reverse('tasks-status-list', request=request, format=format),
|
||||
'tasks/attachments': reverse('tasks-attachment-list', request=request, format=format),
|
||||
'severities': reverse('severity-list', request=request, format=format),
|
||||
'priorities': reverse('priority-list', request=request, format=format),
|
||||
'documents': reverse('document-list', request=request, format=format),
|
||||
'questions': reverse('question-list', request=request, format=format),
|
||||
'question_responses': reverse('question-response-list', request=request, format=format),
|
||||
'wiki/pages': reverse('wiki-page-list', request=request, format=format),
|
||||
'users': reverse('user-list', request=request, format=format),
|
||||
'roles': reverse('user-roles', request=request, format=format),
|
||||
'search': reverse('search', request=request, format=format),
|
||||
})
|
||||
|
||||
|
||||
|
@ -65,11 +70,9 @@ class RoleList(generics.ListCreateAPIView):
|
|||
|
||||
|
||||
class UserFilter(django_filters.FilterSet):
|
||||
is_active = django_filters.BooleanFilter(name="is_active")
|
||||
|
||||
class Meta:
|
||||
model = User
|
||||
fields = ['is_active',]
|
||||
fields = ['is_active']
|
||||
|
||||
|
||||
class UserList(generics.ListCreateAPIView):
|
||||
|
@ -93,6 +96,12 @@ class UserList(generics.ListCreateAPIView):
|
|||
pass
|
||||
|
||||
|
||||
class UserDetail(generics.RetrieveUpdateDestroyAPIView):
|
||||
model = User
|
||||
serializer_class = UserSerializer
|
||||
permission_classes = (IsAuthenticated,)
|
||||
|
||||
|
||||
class Login(APIView):
|
||||
def post(self, request, format=None):
|
||||
username = request.DATA.get('username', None)
|
||||
|
@ -129,3 +138,17 @@ class Logout(APIView):
|
|||
def post(self, request, format=None):
|
||||
logout(request)
|
||||
return Response()
|
||||
|
||||
|
||||
class Search(APIView):
|
||||
def get(self, request, format=None):
|
||||
text = request.QUERY_PARAMS.get('text', None)
|
||||
|
||||
if text:
|
||||
#TODO: permission check
|
||||
results = SearchQuerySet().filter(content=text)[:settings.MAX_SEARCH_RESULTS]
|
||||
return_data = SearchSerializer(results)
|
||||
return Response(return_data.data)
|
||||
|
||||
return Response({"detail": "Parameter text can't be empty"}, status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
|
|
|
@ -134,16 +134,41 @@
|
|||
"questions",
|
||||
"question"
|
||||
],
|
||||
[
|
||||
"can_assign_question_to_myself",
|
||||
"questions",
|
||||
"question"
|
||||
],
|
||||
[
|
||||
"can_assign_question_to_other",
|
||||
"questions",
|
||||
"question"
|
||||
],
|
||||
[
|
||||
"can_change_assigned_question",
|
||||
"questions",
|
||||
"question"
|
||||
],
|
||||
[
|
||||
"can_change_owned_question",
|
||||
"questions",
|
||||
"question"
|
||||
],
|
||||
[
|
||||
"can_change_question_state",
|
||||
"questions",
|
||||
"question"
|
||||
],
|
||||
[
|
||||
"can_reply_question",
|
||||
"questions",
|
||||
"question"
|
||||
],
|
||||
[
|
||||
"can_view_question",
|
||||
"questions",
|
||||
"question"
|
||||
],
|
||||
[
|
||||
"change_question",
|
||||
"questions",
|
||||
|
@ -155,19 +180,34 @@
|
|||
"question"
|
||||
],
|
||||
[
|
||||
"add_questionresponse",
|
||||
"add_questionstatus",
|
||||
"questions",
|
||||
"questionresponse"
|
||||
"questionstatus"
|
||||
],
|
||||
[
|
||||
"change_questionresponse",
|
||||
"change_questionstatus",
|
||||
"questions",
|
||||
"questionresponse"
|
||||
"questionstatus"
|
||||
],
|
||||
[
|
||||
"delete_questionresponse",
|
||||
"delete_questionstatus",
|
||||
"questions",
|
||||
"questionresponse"
|
||||
"questionstatus"
|
||||
],
|
||||
[
|
||||
"add_attachment",
|
||||
"scrum",
|
||||
"attachment"
|
||||
],
|
||||
[
|
||||
"change_attachment",
|
||||
"scrum",
|
||||
"attachment"
|
||||
],
|
||||
[
|
||||
"delete_attachment",
|
||||
"scrum",
|
||||
"attachment"
|
||||
],
|
||||
[
|
||||
"add_issue",
|
||||
|
|
|
@ -6,8 +6,10 @@ from django.utils.cache import patch_vary_headers
|
|||
from django.utils.http import cookie_date
|
||||
from django.utils.importlib import import_module
|
||||
|
||||
from django.contrib.sessions.middleware import SessionMiddleware
|
||||
|
||||
class GreenmineSessionMiddleware(object):
|
||||
|
||||
class GreenmineSessionMiddleware(SessionMiddleware):
|
||||
def process_request(self, request):
|
||||
engine = import_module(settings.SESSION_ENGINE)
|
||||
session_key = request.META.get(settings.SESSION_HEADER_NAME, None)
|
||||
|
@ -15,42 +17,6 @@ class GreenmineSessionMiddleware(object):
|
|||
session_key = request.COOKIES.get(settings.SESSION_COOKIE_NAME, None)
|
||||
request.session = engine.SessionStore(session_key)
|
||||
|
||||
def process_response(self, request, response):
|
||||
"""
|
||||
If request.session was modified, or if the configuration is to save the
|
||||
session every time, save the changes and set a session cookie.
|
||||
"""
|
||||
try:
|
||||
accessed = request.session.accessed
|
||||
modified = request.session.modified
|
||||
except AttributeError:
|
||||
pass
|
||||
else:
|
||||
if accessed:
|
||||
patch_vary_headers(response, ('Cookie',))
|
||||
if modified or settings.SESSION_SAVE_EVERY_REQUEST:
|
||||
if request.session.get_expire_at_browser_close():
|
||||
max_age = None
|
||||
expires = None
|
||||
else:
|
||||
max_age = request.session.get_expiry_age()
|
||||
expires_time = time.time() + max_age
|
||||
expires = cookie_date(expires_time)
|
||||
# Save the session data and refresh the client cookie.
|
||||
# Skip session save for 500 responses, refs #3881.
|
||||
if response.status_code != 500:
|
||||
request.session.save()
|
||||
response.set_cookie(settings.SESSION_COOKIE_NAME,
|
||||
request.session.session_key,
|
||||
max_age=max_age,
|
||||
expires=expires,
|
||||
domain=settings.SESSION_COOKIE_DOMAIN,
|
||||
path=settings.SESSION_COOKIE_PATH,
|
||||
secure=settings.SESSION_COOKIE_SECURE or None,
|
||||
httponly=settings.SESSION_COOKIE_HTTPONLY or None)
|
||||
return response
|
||||
|
||||
|
||||
|
||||
COORS_ALLOWED_ORIGINS = getattr(settings, 'COORS_ALLOWED_ORIGINS', '*')
|
||||
COORS_ALLOWED_METHODS = getattr(settings, 'COORS_ALLOWED_METHODS',
|
||||
|
|
|
@ -31,23 +31,9 @@ def attach_uuid(sender, instance, **kwargs):
|
|||
instance.uuid = unicode(uuid.uuid1())
|
||||
|
||||
|
||||
# Centraliced reference assignation.
|
||||
@receiver(signals.pre_save, sender=Task)
|
||||
@receiver(signals.pre_save, sender=UserStory)
|
||||
def attach_unique_reference(sender, instance, **kwargs):
|
||||
project = Project.objects.select_for_update().filter(pk=instance.project_id).get()
|
||||
if isinstance(instance, Task):
|
||||
project.last_task_ref += 1
|
||||
instance.ref = project.last_task_ref
|
||||
else:
|
||||
project.last_us_ref += 1
|
||||
instance.ref = project.last_us_ref
|
||||
|
||||
project.save()
|
||||
|
||||
|
||||
class User(AbstractUser, WatcherMixin):
|
||||
color = models.CharField(max_length=9, null=False, blank=False,
|
||||
color = models.CharField(max_length=9, null=False, blank=False, default="#669933",
|
||||
verbose_name=_('color'))
|
||||
description = models.TextField(null=False, blank=True,
|
||||
verbose_name=_('description'))
|
||||
|
|
|
@ -54,12 +54,39 @@ class LoginSerializer(serializers.Serializer):
|
|||
|
||||
|
||||
class UserSerializer(serializers.ModelSerializer):
|
||||
projects = serializers.SerializerMethodField('get_projects')
|
||||
|
||||
class Meta:
|
||||
model = User
|
||||
fields = ('id', 'username', 'first_name', 'last_name', 'color', 'is_active',)
|
||||
fields = ('id', 'username', 'first_name', 'last_name', 'email', 'color', 'description',
|
||||
'default_language', 'default_timezone', 'is_active', 'photo', 'projects')
|
||||
|
||||
def get_projects(self, obj):
|
||||
return [x.id for x in obj.projects.all()]
|
||||
|
||||
|
||||
class RoleSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = Role
|
||||
fields = ('id', 'name', 'slug', 'permissions',)
|
||||
|
||||
|
||||
class SearchSerializer(serializers.Serializer):
|
||||
id = serializers.CharField(max_length=255)
|
||||
model_name = serializers.CharField(max_length=255)
|
||||
pk = serializers.IntegerField()
|
||||
score = serializers.FloatField()
|
||||
stored_fields = serializers.SerializerMethodField('get_stored_fields')
|
||||
|
||||
def get_stored_fields(self, obj):
|
||||
return obj.get_stored_fields()
|
||||
|
||||
def restore_object(self, attrs, instance=None):
|
||||
"""
|
||||
Given a dictionary of deserialized field values, either update
|
||||
an existing model instance, or create a new model instance.
|
||||
"""
|
||||
if instance is not None:
|
||||
return instance
|
||||
|
||||
return attrs
|
||||
|
|
|
@ -9,6 +9,8 @@ urlpatterns = format_suffix_patterns(patterns('',
|
|||
url(r'^auth/login/$', api.Login.as_view(), name='login'),
|
||||
url(r'^auth/logout/$', api.Logout.as_view(), name='logout'),
|
||||
url(r'^users/$', api.UserList.as_view(), name="user-list"),
|
||||
url(r'^users/(?P<pk>[0-9]+)/$', api.UserDetail.as_view(), name="user-detail"),
|
||||
url(r'^roles/$', api.RoleList.as_view(), name="user-roles"),
|
||||
url(r'^search/$', api.Search.as_view(), name="search"),
|
||||
url(r'^$', api.ApiRoot.as_view(), name='api_root'),
|
||||
))
|
||||
|
|
|
@ -25,9 +25,6 @@ def slugify_uniquely(value, model, slugfield="slug"):
|
|||
|
||||
|
||||
def ref_uniquely(p, seq_field, model, field='ref'):
|
||||
"""
|
||||
Returns a unique reference code based on base64 and time.
|
||||
"""
|
||||
project = p.__class__.objects.select_for_update().get(pk=p.pk)
|
||||
ref = getattr(project, seq_field) + 1
|
||||
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
from django.contrib import admin
|
||||
|
||||
from greenmine.documents.models import Document
|
||||
from . import models
|
||||
|
||||
|
||||
class DocumentAdmin(admin.ModelAdmin):
|
||||
list_display = ["title", "project", "owner"]
|
||||
|
||||
admin.site.register(Document, DocumentAdmin)
|
||||
admin.site.register(models.Document, DocumentAdmin)
|
||||
|
|
|
@ -1,19 +1,21 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
from rest_framework import generics
|
||||
|
||||
from greenmine.documents.serializers import DocumentSerializer
|
||||
from greenmine.documents.models import Document
|
||||
from greenmine.documents.permissions import DocumentDetailPermission
|
||||
from . import serializers
|
||||
from . import models
|
||||
from . import permissions
|
||||
|
||||
|
||||
class DocumentList(generics.ListCreateAPIView):
|
||||
model = Document
|
||||
serializer_class = DocumentSerializer
|
||||
model = models.Document
|
||||
serializer_class = serializers.DocumentSerializer
|
||||
|
||||
def get_queryset(self):
|
||||
return self.model.objects.filter(project__members=self.request.user)
|
||||
return super(DocumentList, self).filter(project__members=self.request.user)
|
||||
|
||||
|
||||
class DocumentDetail(generics.RetrieveUpdateDestroyAPIView):
|
||||
model = Document
|
||||
serializer_class = DocumentSerializer
|
||||
permission_classes = (DocumentDetailPermission,)
|
||||
model = models.Document
|
||||
serializer_class = serializers.DocumentSerializer
|
||||
permission_classes = (permissions.DocumentDetailPermission,)
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
# -* coding: utf-8 -*-
|
||||
|
||||
from django.db import models
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
|
|
|
@ -1,5 +1,8 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
from greenmine.base.permissions import BaseDetailPermission
|
||||
|
||||
|
||||
class DocumentDetailPermission(BaseDetailPermission):
|
||||
get_permission = "can_view_document"
|
||||
put_permission = "can_change_document"
|
||||
|
|
|
@ -1,13 +1,17 @@
|
|||
# -* coding: utf-8 -*-
|
||||
|
||||
from haystack import indexes
|
||||
from .models import Document
|
||||
|
||||
from . import models
|
||||
|
||||
|
||||
class DocumentIndex(indexes.RealTimeSearchIndex, indexes.Indexable):
|
||||
text = indexes.CharField(document=True, use_template=True, template_name='search/indexes/document_text.txt')
|
||||
class DocumentIndex(indexes.SearchIndex, indexes.Indexable):
|
||||
text = indexes.CharField(document=True, use_template=True,
|
||||
template_name='search/indexes/document_text.txt')
|
||||
title = indexes.CharField(model_attr='title')
|
||||
|
||||
def get_model(self):
|
||||
return Document
|
||||
return models.Document
|
||||
|
||||
def index_queryset(self):
|
||||
def index_queryset(self, using=None):
|
||||
return self.get_model().objects.all()
|
||||
|
|
|
@ -1,8 +1,11 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
from rest_framework import serializers
|
||||
|
||||
from greenmine.documents.models import Document
|
||||
from . import models
|
||||
|
||||
|
||||
class DocumentSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = Document
|
||||
model = models.Document
|
||||
fields = ()
|
||||
|
|
|
@ -1,7 +1,10 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
from django.conf.urls import patterns, url
|
||||
from rest_framework.urlpatterns import format_suffix_patterns
|
||||
|
||||
from greenmine.documents import api
|
||||
from . import api
|
||||
|
||||
|
||||
urlpatterns = format_suffix_patterns(patterns('',
|
||||
url(r'^documents/$', api.DocumentList.as_view(), name='document-list'),
|
||||
|
|
|
@ -1,16 +1,19 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
from django.contrib import admin
|
||||
|
||||
from greenmine.questions.models import Question, QuestionResponse
|
||||
from . import models
|
||||
|
||||
import reversion
|
||||
|
||||
|
||||
class QuestionAdmin(admin.ModelAdmin):
|
||||
class QuestionAdmin(reversion.VersionAdmin):
|
||||
list_display = ["subject", "project", "owner"]
|
||||
|
||||
admin.site.register(Question, QuestionAdmin)
|
||||
admin.site.register(models.Question, QuestionAdmin)
|
||||
|
||||
|
||||
class QuestionResponseAdmin(admin.ModelAdmin):
|
||||
list_display = ["id", "question", "owner"]
|
||||
class QuestionStatusAdmin(admin.ModelAdmin):
|
||||
list_display = ["name", "order", "is_closed", "project"]
|
||||
|
||||
admin.site.register(QuestionResponse, QuestionResponseAdmin)
|
||||
admin.site.register(models.QuestionStatus, QuestionStatusAdmin)
|
||||
|
|
|
@ -1,32 +1,36 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
from rest_framework import generics
|
||||
from rest_framework.permissions import IsAuthenticated
|
||||
|
||||
from greenmine.questions.serializers import QuestionSerializer, QuestionResponseSerializer
|
||||
from greenmine.questions.models import Question, QuestionResponse
|
||||
from greenmine.questions.permissions import QuestionDetailPermission, QuestionResponseDetailPermission
|
||||
from . import serializers
|
||||
from . import models
|
||||
from . import permissions
|
||||
|
||||
import reversion
|
||||
|
||||
|
||||
class QuestionList(generics.ListCreateAPIView):
|
||||
model = Question
|
||||
serializer_class = QuestionSerializer
|
||||
model = models.Question
|
||||
serializer_class = serializers.QuestionSerializer
|
||||
filter_fields = ('project',)
|
||||
permission_classes = (IsAuthenticated,)
|
||||
|
||||
def get_queryset(self):
|
||||
return self.model.objects.filter(project__members=self.request.user)
|
||||
return super(QuestionList, self).filter(project__members=self.request.user)
|
||||
|
||||
|
||||
def pre_save(self, obj):
|
||||
obj.owner = self.request.user
|
||||
|
||||
|
||||
class QuestionDetail(generics.RetrieveUpdateDestroyAPIView):
|
||||
model = Question
|
||||
serializer_class = QuestionSerializer
|
||||
permission_classes = (QuestionDetailPermission,)
|
||||
model = models.Question
|
||||
serializer_class = serializers.QuestionSerializer
|
||||
permission_classes = (IsAuthenticated, permissions.QuestionDetailPermission,)
|
||||
|
||||
|
||||
class QuestionResponseList(generics.ListCreateAPIView):
|
||||
model = QuestionResponse
|
||||
serializer_class = QuestionResponseSerializer
|
||||
|
||||
def get_queryset(self):
|
||||
return self.model.objects.filter(question__project__members=self.request.user)
|
||||
|
||||
class QuestionResponseDetail(generics.RetrieveUpdateDestroyAPIView):
|
||||
model = QuestionResponse
|
||||
serializer_class = QuestionResponseSerializer
|
||||
permission_classes = (QuestionResponseDetailPermission,)
|
||||
def post_save(self, obj, created=False):
|
||||
with reversion.create_revision():
|
||||
if "comment" in self.request.DATA:
|
||||
# Update the comment in the last version
|
||||
reversion.set_comment(self.request.DATA['comment'])
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
QUESTION_STATUS = (
|
||||
(1, _(u"New"), False),
|
||||
(2, _(u"Pending"), False),
|
||||
(3, _(u"Answered"), True),
|
||||
)
|
|
@ -1,15 +1,51 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
from django.db import models
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.utils import timezone
|
||||
from django.dispatch import receiver
|
||||
|
||||
from greenmine.base.utils.slug import slugify_uniquely
|
||||
from greenmine.base.utils.slug import slugify_uniquely, ref_uniquely
|
||||
from greenmine.base.fields import DictField
|
||||
from greenmine.scrum.models import Project
|
||||
|
||||
from picklefield.fields import PickledObjectField
|
||||
from greenmine.questions.choices import QUESTION_STATUS
|
||||
|
||||
|
||||
class QuestionStatus(models.Model):
|
||||
name = models.CharField(max_length=255, null=False, blank=False,
|
||||
verbose_name=_('name'))
|
||||
order = models.IntegerField(default=10, null=False, blank=False,
|
||||
verbose_name=_('order'))
|
||||
is_closed = models.BooleanField(default=False, null=False, blank=True,
|
||||
verbose_name=_('is closed'))
|
||||
project = models.ForeignKey(Project, null=False, blank=False,
|
||||
related_name='question_status',
|
||||
verbose_name=_('project'))
|
||||
|
||||
class Meta:
|
||||
verbose_name = u'question status'
|
||||
verbose_name_plural = u'question status'
|
||||
ordering = ['project', 'name']
|
||||
unique_together = ('project', 'name')
|
||||
|
||||
def __unicode__(self):
|
||||
return u'project {0} - {1}'.format(self.project_id, self.name)
|
||||
|
||||
|
||||
class Question(models.Model):
|
||||
subject = models.CharField(max_length=150, null=False, blank=False,
|
||||
|
||||
ref = models.BigIntegerField(db_index=True, null=True, blank=True, default=None,
|
||||
verbose_name=_('ref'))
|
||||
owner = models.ForeignKey('base.User', null=True, blank=True, default=None,
|
||||
related_name='owned_questions',
|
||||
verbose_name=_('owner'))
|
||||
status = models.ForeignKey('QuestionStatus', null=False, blank=False,
|
||||
related_name='questions',
|
||||
verbose_name=_('status'))
|
||||
subject = models.CharField(max_length=250, null=False, blank=False,
|
||||
verbose_name=_('subject'))
|
||||
slug = models.SlugField(unique=True, max_length=250, null=False, blank=True,
|
||||
verbose_name=_('slug'))
|
||||
content = models.TextField(null=False, blank=True,
|
||||
verbose_name=_('content'))
|
||||
closed = models.BooleanField(default=False, null=False, blank=True,
|
||||
|
@ -18,66 +54,66 @@ class Question(models.Model):
|
|||
upload_to='messages',
|
||||
verbose_name=_('attached_file'))
|
||||
project = models.ForeignKey('scrum.Project', null=False, blank=False,
|
||||
related_name='questions')
|
||||
related_name='questions',
|
||||
verbose_name=_('project'))
|
||||
milestone = models.ForeignKey('scrum.Milestone', null=True, blank=True, default=None,
|
||||
related_name='questions',
|
||||
verbose_name=_('milestone'))
|
||||
assigned_to = models.ForeignKey('base.User', null=False, blank=False,
|
||||
finished_date = models.DateTimeField(null=True, blank=True,
|
||||
verbose_name=_('finished date'))
|
||||
assigned_to = models.ForeignKey('base.User', null=True, blank=True, default=None,
|
||||
related_name='questions_assigned_to_me',
|
||||
verbose_name=_('assigned_to'))
|
||||
created_date = models.DateTimeField(auto_now_add=True, null=False, blank=False,
|
||||
verbose_name=_('created date'))
|
||||
modified_date = models.DateTimeField(auto_now_add=True, null=False, blank=False,
|
||||
verbose_name=_('modified date'))
|
||||
owner = models.ForeignKey('base.User', null=False, blank=False,
|
||||
related_name='owned_questions')
|
||||
watchers = models.ManyToManyField('base.User', null=True, blank=True,
|
||||
related_name='watched_questions',
|
||||
verbose_name=_('watchers'))
|
||||
tags = DictField(null=False, blank=True,
|
||||
tags = PickledObjectField(null=False, blank=True,
|
||||
verbose_name=_('tags'))
|
||||
|
||||
class Meta:
|
||||
verbose_name = u'question'
|
||||
verbose_name_plural = u'questions'
|
||||
ordering = ['project', 'subject', 'id']
|
||||
#TODO: permissions
|
||||
permissions = (
|
||||
('can_reply_question', 'Can reply questions'),
|
||||
('can_change_owned_question', 'Can modify owned questions'),
|
||||
('can_change_assigned_question', 'Can modify assigned questions'),
|
||||
('can_assign_question_to_other', 'Can assign questions to others'),
|
||||
('can_assign_question_to_myself', 'Can assign questions to myself'),
|
||||
('can_change_question_state', 'Can change the question state'),
|
||||
('can_view_question', 'Can view the question'),
|
||||
)
|
||||
|
||||
def __unicode__(self):
|
||||
return self.subject
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
if not self.slug:
|
||||
self.slug = slugify_uniquely(self.subject, self.__class__)
|
||||
if self.id:
|
||||
self.modified_date = timezone.now()
|
||||
|
||||
if not self.ref:
|
||||
self.ref = ref_uniquely(self.project, 'last_issue_ref', self.__class__)
|
||||
|
||||
super(Question, self).save(*args, **kwargs)
|
||||
|
||||
|
||||
class QuestionResponse(models.Model):
|
||||
content = models.TextField(null=False, blank=False,
|
||||
verbose_name=_('content'))
|
||||
created_date = models.DateTimeField(auto_now_add=True, null=False, blank=False,
|
||||
verbose_name=_('created date'))
|
||||
modified_date = models.DateTimeField(auto_now=True, null=False, blank=False,
|
||||
verbose_name=_('modified date'))
|
||||
attached_file = models.FileField(max_length=500, null=True, blank=True,
|
||||
upload_to='messages',
|
||||
verbose_name=_('attached file'))
|
||||
question = models.ForeignKey('Question', null=False, blank=False,
|
||||
related_name='responses',
|
||||
verbose_name=_('question'))
|
||||
owner = models.ForeignKey('base.User', null=False, blank=False,
|
||||
related_name='question_responses')
|
||||
tags = DictField(null=False, blank=True,
|
||||
verbose_name=_('tags'))
|
||||
# Model related signals handlers
|
||||
@receiver(models.signals.post_save, sender=Project, dispatch_uid='project_post_save_add_question_states')
|
||||
def project_post_save_add_question_states(sender, instance, created, **kwargs):
|
||||
"""
|
||||
Create all project model depences on project is
|
||||
created.
|
||||
"""
|
||||
|
||||
class Meta:
|
||||
verbose_name = u'question response'
|
||||
verbose_name_plural = u'question responses'
|
||||
ordering = ['question', 'created_date']
|
||||
|
||||
def __unicode__(self):
|
||||
return u'{0} - response {1}'.format(unicode(self.question), self.id)
|
||||
if not created:
|
||||
return
|
||||
|
||||
# Populate new project dependen default data
|
||||
for order, name, is_closed in QUESTION_STATUS:
|
||||
QuestionStatus.objects.create(name=name, order=order,
|
||||
is_closed=is_closed, project=instance)
|
||||
|
|
|
@ -1,15 +1,13 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
from greenmine.base.permissions import BaseDetailPermission
|
||||
|
||||
|
||||
class QuestionDetailPermission(BaseDetailPermission):
|
||||
get_permission = "can_view_question"
|
||||
put_permission = "can_change_question"
|
||||
delete_permission = "can_delete_question"
|
||||
put_permission = "change_question"
|
||||
patch_permission = "change_question"
|
||||
delete_permission = "delete_question"
|
||||
safe_methods = ['HEAD', 'OPTIONS']
|
||||
path_to_document = []
|
||||
path_to_project = []
|
||||
|
||||
class QuestionResponseDetailPermission(BaseDetailPermission):
|
||||
get_permission = "can_view_questionresponse"
|
||||
put_permission = "can_change_questionresponse"
|
||||
delete_permission = "can_delete_questionresponse"
|
||||
safe_methods = ['HEAD', 'OPTIONS']
|
||||
path_to_document = []
|
||||
|
|
|
@ -1,13 +1,17 @@
|
|||
# -* coding: utf-8 -*-
|
||||
|
||||
from haystack import indexes
|
||||
from .models import Question
|
||||
|
||||
from . import models
|
||||
|
||||
|
||||
class QuestionIndex(indexes.RealTimeSearchIndex, indexes.Indexable):
|
||||
text = indexes.CharField(document=True, use_template=True, template_name='search/indexes/question_text.txt')
|
||||
class QuestionIndex(indexes.SearchIndex, indexes.Indexable):
|
||||
text = indexes.CharField(document=True, use_template=True,
|
||||
template_name='search/indexes/question_text.txt')
|
||||
title = indexes.CharField(model_attr='subject')
|
||||
|
||||
def get_model(self):
|
||||
return Question
|
||||
return models.Question
|
||||
|
||||
def index_queryset(self):
|
||||
def index_queryset(self, using=None):
|
||||
return self.get_model().objects.all()
|
||||
|
|
|
@ -1,15 +1,59 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
from rest_framework import serializers
|
||||
|
||||
from greenmine.questions.models import Question, QuestionResponse
|
||||
import reversion
|
||||
|
||||
from greenmine.scrum.serializers import PickleField
|
||||
|
||||
from . import models
|
||||
|
||||
|
||||
class QuestionSerializer(serializers.ModelSerializer):
|
||||
tags = PickleField()
|
||||
comment = serializers.SerializerMethodField('get_comment')
|
||||
history = serializers.SerializerMethodField('get_history')
|
||||
|
||||
class Meta:
|
||||
model = Question
|
||||
model = models.Question
|
||||
fields = ()
|
||||
|
||||
def get_comment(self, obj):
|
||||
return ''
|
||||
|
||||
class QuestionResponseSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = QuestionResponse
|
||||
fields = ()
|
||||
def get_questions_diff(self, old_question_version, new_question_version):
|
||||
old_obj = old_question_version.field_dict
|
||||
new_obj = new_question_version.field_dict
|
||||
|
||||
diff_dict = {
|
||||
'modified_date': new_obj['modified_date'],
|
||||
'by': old_question_version.revision.user,
|
||||
'comment': old_question_version.revision.comment,
|
||||
}
|
||||
|
||||
for key in old_obj.keys():
|
||||
if key == 'modified_date':
|
||||
continue
|
||||
|
||||
if old_obj[key] == new_obj[key]:
|
||||
continue
|
||||
|
||||
diff_dict[key] = {
|
||||
'old': old_obj[key],
|
||||
'new': new_obj[key],
|
||||
}
|
||||
|
||||
return diff_dict
|
||||
|
||||
def get_history(self, obj):
|
||||
diff_list = []
|
||||
current = None
|
||||
|
||||
for version in reversed(list(reversion.get_for_object(obj))):
|
||||
if current:
|
||||
questions_diff = self.get_questions_diff(version, current)
|
||||
diff_list.append(questions_diff)
|
||||
|
||||
current = version
|
||||
|
||||
return diff_list
|
||||
|
|
|
@ -1,12 +1,13 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
from django.conf.urls import patterns, url
|
||||
from rest_framework.urlpatterns import format_suffix_patterns
|
||||
|
||||
from greenmine.questions import api
|
||||
from . import api
|
||||
|
||||
|
||||
urlpatterns = format_suffix_patterns(patterns('',
|
||||
url(r'^questions/$', api.QuestionList.as_view(), name='question-list'),
|
||||
url(r'^questions/(?P<pk>[0-9]+)/$', api.QuestionDetail.as_view(), name='question-detail'),
|
||||
url(r'^question_responses/$', api.QuestionResponseList.as_view(), name='question-response-list'),
|
||||
url(r'^question_responses/(?P<pk>[0-9]+)/$', api.QuestionResponseDetail.as_view(), name='question-response-detail'),
|
||||
))
|
||||
|
||||
|
|
|
@ -30,6 +30,7 @@ class UserStoryInline(admin.TabularInline):
|
|||
else:
|
||||
return models.UserStory.objects.none()
|
||||
|
||||
|
||||
class ProjectAdmin(reversion.VersionAdmin):
|
||||
list_display = ["name", "owner"]
|
||||
inlines = [MembershipInline, MilestoneInline, UserStoryInline]
|
||||
|
@ -57,7 +58,7 @@ admin.site.register(models.Attachment, AttachmentAdmin)
|
|||
|
||||
|
||||
class TaskAdmin(reversion.VersionAdmin):
|
||||
list_display = ["subject", "user_story", "milestone", "project", "user_story_id"]
|
||||
list_display = ["subject", "ref", "user_story", "milestone", "project", "user_story_id"]
|
||||
list_filter = ["user_story", "milestone", "project"]
|
||||
|
||||
def user_story_id(self, instance):
|
||||
|
@ -67,30 +68,39 @@ class MembershipAdmin(admin.ModelAdmin):
|
|||
list_display = ['project', 'role', 'user']
|
||||
list_filter = ['project', 'role']
|
||||
|
||||
|
||||
class IssueAdmin(reversion.VersionAdmin):
|
||||
list_display = ["subject", "type"]
|
||||
|
||||
|
||||
class SeverityAdmin(admin.ModelAdmin):
|
||||
list_display = ["name", "order", "project"]
|
||||
|
||||
|
||||
class PriorityAdmin(admin.ModelAdmin):
|
||||
list_display = ["name", "order", "project"]
|
||||
|
||||
|
||||
class PointsAdmin(admin.ModelAdmin):
|
||||
list_display = ["name", "order", "project"]
|
||||
|
||||
|
||||
class IssueTypeAdmin(admin.ModelAdmin):
|
||||
list_display = ["name", "order", "project"]
|
||||
|
||||
|
||||
class IssueStatusAdmin(admin.ModelAdmin):
|
||||
list_display = ["name", "order", "is_closed", "project"]
|
||||
|
||||
|
||||
class TaskStatusAdmin(admin.ModelAdmin):
|
||||
list_display = ["name", "order", "is_closed", "project"]
|
||||
|
||||
|
||||
class UserStoryStatusAdmin(admin.ModelAdmin):
|
||||
list_display = ["name", "order", "is_closed", "project"]
|
||||
|
||||
|
||||
admin.site.register(models.Task, TaskAdmin)
|
||||
admin.site.register(models.Issue, IssueAdmin)
|
||||
|
||||
|
|
|
@ -1,5 +1,8 @@
|
|||
import django_filters
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from django.db.models import Q
|
||||
|
||||
import django_filters
|
||||
from rest_framework import generics
|
||||
from rest_framework.permissions import IsAuthenticated
|
||||
|
||||
|
@ -50,7 +53,9 @@ class ProjectList(generics.ListCreateAPIView):
|
|||
permission_classes = (IsAuthenticated,)
|
||||
|
||||
def get_queryset(self):
|
||||
return self.model.objects.filter(members=self.request.user)
|
||||
return self.model.objects.filter(
|
||||
Q(owner=self.request.user) | Q(members=self.request.user)
|
||||
)
|
||||
|
||||
def pre_save(self, obj):
|
||||
obj.owner = self.request.user
|
||||
|
@ -100,7 +105,7 @@ class UserStoryDetail(generics.RetrieveUpdateDestroyAPIView):
|
|||
permission_classes = (IsAuthenticated, UserStoryDetailPermission,)
|
||||
|
||||
|
||||
class IssuesAttachmentFilter(django_filters.FilterSet):
|
||||
class AttachmentFilter(django_filters.FilterSet):
|
||||
class Meta:
|
||||
model = Attachment
|
||||
fields = ['project', 'object_id']
|
||||
|
@ -110,7 +115,7 @@ class IssuesAttachmentList(generics.ListCreateAPIView):
|
|||
model = Attachment
|
||||
serializer_class = AttachmentSerializer
|
||||
permission_classes = (IsAuthenticated,)
|
||||
filter_class = IssuesAttachmentFilter
|
||||
filter_class = AttachmentFilter
|
||||
|
||||
def get_queryset(self):
|
||||
ct = ContentType.objects.get_for_model(Issue)
|
||||
|
@ -129,6 +134,29 @@ class IssuesAttachmentDetail(generics.RetrieveUpdateDestroyAPIView):
|
|||
permission_classes = (IsAuthenticated, AttachmentDetailPermission,)
|
||||
|
||||
|
||||
class TasksAttachmentList(generics.ListCreateAPIView):
|
||||
model = Attachment
|
||||
serializer_class = AttachmentSerializer
|
||||
permission_classes = (IsAuthenticated,)
|
||||
filter_class = AttachmentFilter
|
||||
|
||||
def get_queryset(self):
|
||||
ct = ContentType.objects.get_for_model(Task)
|
||||
return super(TasksAttachmentList, self).get_queryset()\
|
||||
.filter(project__members=self.request.user)\
|
||||
.filter(content_type=ct)
|
||||
|
||||
def pre_save(self, obj):
|
||||
obj.content_type = ContentType.objects.get_for_model(Task)
|
||||
obj.owner = self.request.user
|
||||
|
||||
|
||||
class TasksAttachmentDetail(generics.RetrieveUpdateDestroyAPIView):
|
||||
model = Attachment
|
||||
serializer_class = AttachmentSerializer
|
||||
permission_classes = (IsAuthenticated, AttachmentDetailPermission,)
|
||||
|
||||
|
||||
class TaskList(generics.ListCreateAPIView):
|
||||
model = Task
|
||||
serializer_class = TaskSerializer
|
||||
|
@ -140,6 +168,7 @@ class TaskList(generics.ListCreateAPIView):
|
|||
|
||||
def pre_save(self, obj):
|
||||
obj.owner = self.request.user
|
||||
obj.milestone = obj.user_story.milestone
|
||||
|
||||
|
||||
class TaskDetail(generics.RetrieveUpdateDestroyAPIView):
|
||||
|
@ -147,6 +176,12 @@ class TaskDetail(generics.RetrieveUpdateDestroyAPIView):
|
|||
serializer_class = TaskSerializer
|
||||
permission_classes = (IsAuthenticated, TaskDetailPermission,)
|
||||
|
||||
def post_save(self, obj, created=False):
|
||||
with reversion.create_revision():
|
||||
if "comment" in self.request.DATA:
|
||||
# Update the comment in the last version
|
||||
reversion.set_comment(self.request.DATA['comment'])
|
||||
|
||||
|
||||
class IssueList(generics.ListCreateAPIView):
|
||||
model = Issue
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
|
||||
PRIORITY_CHOICES = (
|
||||
(1, _(u'Low')),
|
||||
(3, _(u'Normal')),
|
||||
|
|
|
@ -3,6 +3,8 @@
|
|||
import random
|
||||
import datetime
|
||||
|
||||
from sampledatahelper.helper import SampleDataHelper
|
||||
|
||||
from django.core.management.base import BaseCommand
|
||||
from django.db import transaction
|
||||
from django.utils.timezone import now
|
||||
|
@ -11,6 +13,8 @@ from django.contrib.webdesign import lorem_ipsum
|
|||
|
||||
from greenmine.base.models import User, Role
|
||||
from greenmine.scrum.models import *
|
||||
from greenmine.questions.models import *
|
||||
|
||||
|
||||
subjects = [
|
||||
"Fixing templates for Django 1.2.",
|
||||
|
@ -29,122 +33,170 @@ subjects = [
|
|||
|
||||
|
||||
class Command(BaseCommand):
|
||||
def create_user(self, counter):
|
||||
user = User.objects.create(
|
||||
username='user%d' % (counter),
|
||||
first_name='user%d' % (counter),
|
||||
email='foouser%d@domain.com' % (counter),
|
||||
token=''.join(random.sample('abcdef0123456789', 10)),
|
||||
)
|
||||
|
||||
user.set_password('user%d' % (counter))
|
||||
user.save()
|
||||
return user
|
||||
sd = SampleDataHelper(seed=12345678901)
|
||||
|
||||
@transaction.commit_on_success
|
||||
def handle(self, *args, **options):
|
||||
users = [User.objects.get(is_superuser=True)]
|
||||
self.users = [User.objects.get(is_superuser=True)]
|
||||
for x in range(10):
|
||||
users.append(self.create_user(x))
|
||||
self.users.append(self.create_user(x))
|
||||
|
||||
role = Role.objects.all()[0]
|
||||
|
||||
# projects
|
||||
for x in xrange(3):
|
||||
# create project
|
||||
project = Project(
|
||||
name='Project Example 1 %s' % (x),
|
||||
description='Project example %s description' % (x),
|
||||
owner=random.choice(users),
|
||||
public=True,
|
||||
)
|
||||
project = self.create_project(x)
|
||||
|
||||
project.save()
|
||||
|
||||
for user in users:
|
||||
for user in self.users:
|
||||
Membership.objects.create(project=project, role=role, user=user)
|
||||
|
||||
now_date = now() - datetime.timedelta(30)
|
||||
start_date = now() - datetime.timedelta(35)
|
||||
|
||||
# create random milestones
|
||||
for y in xrange(2):
|
||||
milestone = Milestone.objects.create(
|
||||
project=project,
|
||||
name='Sprint %s' % (y),
|
||||
owner=project.owner,
|
||||
created_date=now_date,
|
||||
modified_date=now_date,
|
||||
estimated_start=now_date,
|
||||
estimated_finish=now_date + datetime.timedelta(15),
|
||||
order=10
|
||||
)
|
||||
|
||||
now_date = now_date + datetime.timedelta(15)
|
||||
for y in xrange(self.sd.int(1, 5)):
|
||||
end_date = start_date + datetime.timedelta(15)
|
||||
milestone = self.create_milestone(project, start_date, end_date)
|
||||
|
||||
# create uss asociated to milestones
|
||||
for z in xrange(5):
|
||||
us = UserStory.objects.create(
|
||||
subject=lorem_ipsum.words(random.randint(4, 9), common=False),
|
||||
project=project,
|
||||
owner=random.choice(users),
|
||||
description=lorem_ipsum.words(30, common=False),
|
||||
milestone=milestone,
|
||||
status=UserStoryStatus.objects.get(project=project, order=2),
|
||||
points=Points.objects.get(project=project, order=3),
|
||||
tags=[]
|
||||
)
|
||||
for z in xrange(self.sd.int(3, 7)):
|
||||
us = self.create_us(project, milestone)
|
||||
|
||||
for tag in lorem_ipsum.words(random.randint(1, 5), common=True).split(" "):
|
||||
us.tags.append(tag)
|
||||
for w in xrange(self.sd.int(0,6)):
|
||||
if start_date <= now() and end_date <= now():
|
||||
task = self.create_task(project, milestone, us, start_date, end_date, closed=True)
|
||||
elif start_date <= now() and end_date >= now():
|
||||
task = self.create_task(project, milestone, us, start_date, now())
|
||||
else:
|
||||
# No task on not initiated sprints
|
||||
pass
|
||||
|
||||
us.save()
|
||||
|
||||
for w in xrange(3):
|
||||
Task.objects.create(
|
||||
subject="Task %s" % (w),
|
||||
description=lorem_ipsum.words(30, common=False),
|
||||
project=project,
|
||||
owner=random.choice(users),
|
||||
milestone=milestone,
|
||||
user_story=us,
|
||||
severity=Severity.objects.get(project=project, order=2),
|
||||
status=TaskStatus.objects.get(project=project, order=4),
|
||||
priority=Priority.objects.get(project=project, order=3),
|
||||
)
|
||||
start_date = end_date
|
||||
|
||||
# created unassociated uss.
|
||||
for y in xrange(10):
|
||||
us = UserStory.objects.create(
|
||||
subject=lorem_ipsum.words(random.randint(4, 9), common=False),
|
||||
status=UserStoryStatus.objects.get(project=project, order=2),
|
||||
points=Points.objects.get(project=project, order=3),
|
||||
owner=random.choice(users),
|
||||
description=lorem_ipsum.words(30, common=False),
|
||||
milestone=None,
|
||||
project=project,
|
||||
tags=[],
|
||||
)
|
||||
|
||||
for tag in lorem_ipsum.words(random.randint(1, 5), common=True).split(" "):
|
||||
us.tags.append(tag)
|
||||
|
||||
us.save()
|
||||
for y in xrange(self.sd.int(8,15)):
|
||||
us = self.create_us(project)
|
||||
|
||||
# create bugs.
|
||||
for y in xrange(20):
|
||||
bug = Issue.objects.create(
|
||||
project=project,
|
||||
subject=lorem_ipsum.words(random.randint(1, 5), common=False),
|
||||
description=lorem_ipsum.words(random.randint(1, 15), common=False),
|
||||
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=3),
|
||||
type=IssueType.objects.get(project=project, order=1),
|
||||
tags=[],
|
||||
)
|
||||
for y in xrange(self.sd.int(15,25)):
|
||||
bug = self.create_bug(project)
|
||||
|
||||
for tag in lorem_ipsum.words(random.randint(1, 5), common=True).split(" "):
|
||||
bug.tags.append(tag)
|
||||
# create questions.
|
||||
for y in xrange(self.sd.int(15,25)):
|
||||
question = self.create_question(project)
|
||||
|
||||
bug.save()
|
||||
def create_question(self, project):
|
||||
question = Question.objects.create(
|
||||
project=project,
|
||||
subject=self.sd.words(1,5),
|
||||
content=self.sd.paragraph(),
|
||||
owner=project.owner,
|
||||
status=self.sd.db_object_from_queryset(QuestionStatus.objects.filter(project=project)),
|
||||
tags=[],
|
||||
)
|
||||
|
||||
for tag in self.sd.words(1,5).split(" "):
|
||||
question.tags.append(tag)
|
||||
|
||||
question.save()
|
||||
|
||||
def create_bug(self, project):
|
||||
bug = Issue.objects.create(
|
||||
project=project,
|
||||
subject=self.sd.words(1, 5),
|
||||
description=self.sd.paragraph(),
|
||||
owner=project.owner,
|
||||
severity=self.sd.db_object_from_queryset(Severity.objects.filter(project=project)),
|
||||
status=self.sd.db_object_from_queryset(IssueStatus.objects.filter(project=project)),
|
||||
priority=self.sd.db_object_from_queryset(Priority.objects.filter(project=project)),
|
||||
type=self.sd.db_object_from_queryset(IssueType.objects.filter(project=project)),
|
||||
tags=[],
|
||||
)
|
||||
|
||||
for tag in self.sd.words(1, 5).split(" "):
|
||||
bug.tags.append(tag)
|
||||
|
||||
bug.save()
|
||||
return bug
|
||||
|
||||
def create_task(self, project, milestone, us, min_date, max_date, closed=False):
|
||||
task = Task(
|
||||
subject="Task {0}".format(self.sd.words(3,4)),
|
||||
description=self.sd.paragraph(),
|
||||
project=project,
|
||||
owner=self.sd.choice(self.users),
|
||||
milestone=milestone,
|
||||
user_story=us,
|
||||
finished_date=None,
|
||||
)
|
||||
if closed:
|
||||
task.status = TaskStatus.objects.get(project=project, order=4)
|
||||
else:
|
||||
task.status = self.sd.db_object_from_queryset(TaskStatus.objects.filter(project=project))
|
||||
|
||||
if task.status.is_closed:
|
||||
task.finished_date = self.sd.datetime_between(min_date, max_date)
|
||||
|
||||
task.save()
|
||||
return task
|
||||
|
||||
def create_us(self, project, milestone=None):
|
||||
us = UserStory(
|
||||
subject=self.sd.words(4,9),
|
||||
project=project,
|
||||
owner=self.sd.choice(self.users),
|
||||
description=self.sd.paragraph(),
|
||||
milestone=milestone,
|
||||
status=self.sd.db_object_from_queryset(UserStoryStatus.objects.filter(project=project)),
|
||||
tags=[]
|
||||
)
|
||||
if milestone:
|
||||
us.points=self.sd.db_object_from_queryset(Points.objects.filter(project=project).exclude(order=0))
|
||||
else:
|
||||
us.points=self.sd.db_object_from_queryset(Points.objects.filter(project=project))
|
||||
|
||||
for tag in self.sd.words().split(" "):
|
||||
us.tags.append(tag)
|
||||
|
||||
us.save()
|
||||
return us
|
||||
|
||||
def create_milestone(self, project, start_date, end_date):
|
||||
milestone = Milestone(
|
||||
project=project,
|
||||
name='Sprint {0}'.format(start_date),
|
||||
owner=project.owner,
|
||||
created_date=start_date,
|
||||
modified_date=start_date,
|
||||
estimated_start=start_date,
|
||||
estimated_finish=end_date,
|
||||
order=10
|
||||
)
|
||||
milestone.save()
|
||||
return milestone
|
||||
|
||||
def create_project(self, counter):
|
||||
# create project
|
||||
project = Project(
|
||||
name='Project Example 1 {0}'.format(counter),
|
||||
description='Project example {0} description'.format(counter),
|
||||
owner=random.choice(self.users),
|
||||
public=True,
|
||||
total_story_points=self.sd.int(100, 150),
|
||||
sprints=self.sd.int(5,10)
|
||||
)
|
||||
|
||||
project.save()
|
||||
return project
|
||||
|
||||
def create_user(self, counter):
|
||||
user = User.objects.create(
|
||||
username='user-{0}-{1}'.format(counter, self.sd.word()),
|
||||
first_name=self.sd.name('es'),
|
||||
last_name=self.sd.surname('es'),
|
||||
email=self.sd.email(),
|
||||
token=''.join(random.sample('abcdef0123456789', 10)),
|
||||
)
|
||||
|
||||
user.set_password('user{0}'.format(counter))
|
||||
user.save()
|
||||
return user
|
||||
|
|
|
@ -158,6 +158,15 @@ class Points(models.Model):
|
|||
def __unicode__(self):
|
||||
return u'project {0} - {1}'.format(self.project_id, self.name)
|
||||
|
||||
@property
|
||||
def value(self):
|
||||
if self.order == -2:
|
||||
return 0.5
|
||||
elif self.order == -1:
|
||||
return 1
|
||||
else:
|
||||
return self.order
|
||||
|
||||
|
||||
class Membership(models.Model):
|
||||
user = models.ForeignKey('base.User', null=False, blank=False)
|
||||
|
@ -181,7 +190,7 @@ class Project(models.Model, WatchedMixin):
|
|||
verbose_name=_('created date'))
|
||||
modified_date = models.DateTimeField(auto_now=True, null=False, blank=False,
|
||||
verbose_name=_('modified date'))
|
||||
owner = models.ForeignKey('base.User', null=False, blank=True,
|
||||
owner = models.ForeignKey('base.User', null=False, blank=False,
|
||||
related_name='owned_projects',
|
||||
verbose_name=_('owner'))
|
||||
members = models.ManyToManyField('base.User', related_name='projects', through='Membership',
|
||||
|
@ -240,20 +249,42 @@ class Project(models.Model, WatchedMixin):
|
|||
'tags': self.tags,
|
||||
}
|
||||
|
||||
@property
|
||||
def list_of_milestones(self):
|
||||
return [
|
||||
{
|
||||
'name': milestone.name,
|
||||
'finish_date': milestone.estimated_finish,
|
||||
'closed_points': milestone.closed_points,
|
||||
'client_increment_points': milestone.client_increment_points,
|
||||
'team_increment_points': milestone.team_increment_points
|
||||
} for milestone in self.milestones.all().order_by('estimated_start')
|
||||
]
|
||||
|
||||
|
||||
class Milestone(models.Model, WatchedMixin):
|
||||
uuid = models.CharField(max_length=40, unique=True, null=False, blank=True,
|
||||
uuid = models.CharField(
|
||||
max_length=40, unique=True, null=False, blank=True,
|
||||
verbose_name=_('uuid'))
|
||||
name = models.CharField(max_length=200, db_index=True, null=False, blank=False,
|
||||
|
||||
name = models.CharField(
|
||||
max_length=200, db_index=True, null=False, blank=False,
|
||||
verbose_name=_('name'))
|
||||
|
||||
slug = models.SlugField(max_length=250, unique=True, null=False, blank=True,
|
||||
verbose_name=_('slug'))
|
||||
owner = models.ForeignKey('base.User', null=True, blank=False,
|
||||
related_name='owned_milestones',
|
||||
verbose_name=_('owner'))
|
||||
project = models.ForeignKey('Project', null=False, blank=False,
|
||||
|
||||
owner = models.ForeignKey(
|
||||
'base.User',
|
||||
null=True, blank=True,
|
||||
related_name='owned_milestones', verbose_name=_('owner'))
|
||||
|
||||
project = models.ForeignKey(
|
||||
'Project',
|
||||
null=False, blank=False,
|
||||
related_name='milestones',
|
||||
verbose_name=_('project'))
|
||||
|
||||
estimated_start = models.DateField(null=True, blank=True, default=None,
|
||||
verbose_name=_('estimated start'))
|
||||
estimated_finish = models.DateField(null=True, blank=True, default=None,
|
||||
|
@ -304,6 +335,47 @@ class Milestone(models.Model, WatchedMixin):
|
|||
'modified_date': self.modified_date,
|
||||
}
|
||||
|
||||
@property
|
||||
def closed_points(self):
|
||||
points = [ us.points.value for us in self.user_stories.all() if us.is_closed ]
|
||||
return sum(points)
|
||||
|
||||
@property
|
||||
def client_increment_points(self):
|
||||
user_stories = UserStory.objects.filter(
|
||||
created_date__gte=self.estimated_start,
|
||||
created_date__lt=self.estimated_finish,
|
||||
project_id = self.project_id,
|
||||
client_requirement=True,
|
||||
team_requirement=False
|
||||
)
|
||||
points = [ us.points.value for us in user_stories ]
|
||||
return sum(points) + (self.shared_increment_points / 2)
|
||||
|
||||
@property
|
||||
def team_increment_points(self):
|
||||
user_stories = UserStory.objects.filter(
|
||||
created_date__gte=self.estimated_start,
|
||||
created_date__lt=self.estimated_finish,
|
||||
project_id = self.project_id,
|
||||
client_requirement=False,
|
||||
team_requirement=True
|
||||
)
|
||||
points = [ us.points.value for us in user_stories ]
|
||||
return sum(points) + (self.shared_increment_points / 2)
|
||||
|
||||
@property
|
||||
def shared_increment_points(self):
|
||||
user_stories = UserStory.objects.filter(
|
||||
created_date__gte=self.estimated_start,
|
||||
created_date__lt=self.estimated_finish,
|
||||
project_id = self.project_id,
|
||||
client_requirement=True,
|
||||
team_requirement=True
|
||||
)
|
||||
points = [ us.points.value for us in user_stories ]
|
||||
return sum(points)
|
||||
|
||||
|
||||
class UserStory(WatchedMixin, models.Model):
|
||||
uuid = models.CharField(max_length=40, unique=True, null=False, blank=True,
|
||||
|
@ -421,8 +493,7 @@ class Attachment(models.Model):
|
|||
|
||||
def __unicode__(self):
|
||||
return u'content_type {0} - object_id {1} - attachment {2}'.format(
|
||||
self.content_type, self.object_id, self.id
|
||||
)
|
||||
self.content_type, self.object_id, self.id)
|
||||
|
||||
|
||||
class Task(models.Model, WatchedMixin):
|
||||
|
@ -436,12 +507,6 @@ class Task(models.Model, WatchedMixin):
|
|||
owner = models.ForeignKey('base.User', null=True, blank=True, default=None,
|
||||
related_name='owned_tasks',
|
||||
verbose_name=_('owner'))
|
||||
severity = models.ForeignKey('Severity', null=False, blank=False,
|
||||
related_name='tasks',
|
||||
verbose_name=_('severity'))
|
||||
priority = models.ForeignKey('Priority', null=False, blank=False,
|
||||
related_name='tasks',
|
||||
verbose_name=_('priority'))
|
||||
status = models.ForeignKey('TaskStatus', null=False, blank=False,
|
||||
related_name='tasks',
|
||||
verbose_name=_('status'))
|
||||
|
@ -469,6 +534,8 @@ class Task(models.Model, WatchedMixin):
|
|||
verbose_name=_('watchers'))
|
||||
tags = PickledObjectField(null=False, blank=True,
|
||||
verbose_name=_('tags'))
|
||||
is_iocaine = models.BooleanField(default=False, null=False, blank=True,
|
||||
verbose_name=_('is iocaine'))
|
||||
|
||||
class Meta:
|
||||
verbose_name = u'task'
|
||||
|
@ -493,9 +560,6 @@ class Task(models.Model, WatchedMixin):
|
|||
if self.id:
|
||||
self.modified_date = timezone.now()
|
||||
|
||||
if not self.ref:
|
||||
self.ref = ref_uniquely(self.project, 'last_task_ref', self.__class__)
|
||||
|
||||
super(Task, self).save(*args, **kwargs)
|
||||
|
||||
def _get_watchers_by_role(self):
|
||||
|
@ -574,9 +638,6 @@ class Issue(models.Model, WatchedMixin):
|
|||
if self.id:
|
||||
self.modified_date = timezone.now()
|
||||
|
||||
if not self.ref:
|
||||
self.ref = ref_uniquely(self.project, 'last_issue_ref', self.__class__)
|
||||
|
||||
super(Issue, self).save(*args, **kwargs)
|
||||
|
||||
def _get_watchers_by_role(self):
|
||||
|
@ -587,6 +648,10 @@ class Issue(models.Model, WatchedMixin):
|
|||
'project_owner': (self.project, self.project.owner),
|
||||
}
|
||||
|
||||
@property
|
||||
def is_closed(self):
|
||||
return self.status.is_closed
|
||||
|
||||
|
||||
# Model related signals handlers
|
||||
|
||||
|
@ -626,16 +691,44 @@ def project_post_save(sender, instance, created, **kwargs):
|
|||
IssueType.objects.create(project=instance, name=name, order=order)
|
||||
|
||||
|
||||
@receiver(models.signals.pre_save, sender=Task, dispatch_uid='task_ref_handler')
|
||||
def task_ref_handler(sender, instance, **kwargs):
|
||||
if not instance.id and instance.project:
|
||||
instance.ref = ref_uniquely(instance.project, 'last_task_ref', instance.__class__)
|
||||
|
||||
|
||||
@receiver(models.signals.pre_save, sender=Issue, dispatch_uid='issue_ref_handler')
|
||||
def issue_ref_handler(sender, instance, **kwargs):
|
||||
if not instance.id and instance.project:
|
||||
instance.ref = ref_uniquely(instance.project, 'last_issue_ref', instance.__class__)
|
||||
|
||||
|
||||
@receiver(models.signals.pre_save, sender=UserStory, dispatch_uid='user_story_ref_handler')
|
||||
def user_story_ref_handler(sender, instance, **kwargs):
|
||||
def us_ref_handler(sender, instance, **kwargs):
|
||||
if not instance.id and instance.project:
|
||||
instance.ref = ref_uniquely(instance.project, 'last_us_ref', instance.__class__)
|
||||
|
||||
|
||||
@receiver(models.signals.pre_save, sender=Task, dispatch_uid='tasks_close_handler')
|
||||
def tasks_close_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__)
|
||||
|
||||
if instance.id:
|
||||
if sender.objects.get(id=instance.id).status.is_closed == False and instance.status.is_closed == True:
|
||||
instance.finished_date = timezone.now()
|
||||
if all([task.status.is_closed for task in instance.user_story.tasks.exclude(id=instance.id)]):
|
||||
instance.user_story.finish_date = timezone.now()
|
||||
instance.user_story.save()
|
||||
elif sender.objects.get(id=instance.id).status.is_closed == True and instance.status.is_closed == False:
|
||||
instance.finished_date = None
|
||||
instance.user_story.finish_date = None
|
||||
instance.user_story.save()
|
||||
else:
|
||||
instance.user_story.finish_date = None
|
||||
instance.user_story.save()
|
||||
|
||||
# Email alerts signals handlers
|
||||
# TODO: temporary commented (Pending refactor)
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
from greenmine.base.permissions import BaseDetailPermission
|
||||
|
||||
|
||||
|
@ -41,7 +43,7 @@ class IssueDetailPermission(BaseDetailPermission):
|
|||
get_permission = "can_view_issue"
|
||||
put_permission = "change_issue"
|
||||
patch_permission = "change_issue"
|
||||
delete_permission = "can_delete_issue"
|
||||
delete_permission = "delete_issue"
|
||||
safe_methods = ['HEAD', 'OPTIONS']
|
||||
path_to_project = ['project']
|
||||
|
||||
|
|
|
@ -1,23 +1,28 @@
|
|||
# -* coding: utf-8 -*-
|
||||
|
||||
from haystack import indexes
|
||||
from greenmine.scrum.models import UserStory, Task
|
||||
|
||||
|
||||
class UserStoryIndex(indexes.SearchIndex, indexes.Indexable):
|
||||
text = indexes.CharField(document=True, use_template=True, template_name='search/indexes/userstory_text.txt')
|
||||
text = indexes.CharField(document=True, use_template=True,
|
||||
template_name='search/indexes/userstory_text.txt')
|
||||
title = indexes.CharField(model_attr='subject')
|
||||
|
||||
def get_model(self):
|
||||
return UserStory
|
||||
|
||||
def index_queryset(self):
|
||||
def index_queryset(self, using=None):
|
||||
return self.get_model().objects.all()
|
||||
|
||||
|
||||
class TaskIndex(indexes.SearchIndex, indexes.Indexable):
|
||||
text = indexes.CharField(document=True, use_template=True, template_name='search/indexes/task_text.txt')
|
||||
text = indexes.CharField(document=True, use_template=True,
|
||||
template_name='search/indexes/task_text.txt')
|
||||
title = indexes.CharField(model_attr='subject')
|
||||
|
||||
def get_model(self):
|
||||
return Task
|
||||
|
||||
def index_queryset(self):
|
||||
def index_queryset(self, using=None):
|
||||
return self.get_model().objects.all()
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
from rest_framework import serializers
|
||||
|
||||
from greenmine.scrum.models import *
|
||||
|
@ -5,6 +7,7 @@ from picklefield.fields import dbsafe_encode, dbsafe_decode
|
|||
|
||||
import json, reversion
|
||||
|
||||
|
||||
class PickleField(serializers.WritableField):
|
||||
"""
|
||||
Pickle objects serializer.
|
||||
|
@ -24,6 +27,7 @@ class PointsSerializer(serializers.ModelSerializer):
|
|||
|
||||
class ProjectSerializer(serializers.ModelSerializer):
|
||||
tags = PickleField()
|
||||
list_of_milestones = serializers.Field(source='list_of_milestones')
|
||||
|
||||
class Meta:
|
||||
model = Project
|
||||
|
@ -63,17 +67,60 @@ class AttachmentSerializer(serializers.ModelSerializer):
|
|||
|
||||
|
||||
class TaskSerializer(serializers.ModelSerializer):
|
||||
tags = PickleField()
|
||||
tags = PickleField(blank=True, default=[])
|
||||
comment = serializers.SerializerMethodField('get_comment')
|
||||
history = serializers.SerializerMethodField('get_history')
|
||||
|
||||
class Meta:
|
||||
model = Task
|
||||
fields = ()
|
||||
|
||||
def get_comment(self, obj):
|
||||
return ''
|
||||
|
||||
def get_issues_diff(self, old_issue_version, new_issue_version):
|
||||
old_obj = old_issue_version.field_dict
|
||||
new_obj = new_issue_version.field_dict
|
||||
|
||||
diff_dict = {
|
||||
'modified_date': new_obj['modified_date'],
|
||||
'by': old_issue_version.revision.user,
|
||||
'comment': old_issue_version.revision.comment,
|
||||
}
|
||||
|
||||
for key in old_obj.keys():
|
||||
if key == 'modified_date':
|
||||
continue
|
||||
|
||||
if old_obj[key] == new_obj[key]:
|
||||
continue
|
||||
|
||||
diff_dict[key] = {
|
||||
'old': old_obj[key],
|
||||
'new': new_obj[key],
|
||||
}
|
||||
|
||||
return diff_dict
|
||||
|
||||
def get_history(self, obj):
|
||||
diff_list = []
|
||||
current = None
|
||||
|
||||
for version in reversed(list(reversion.get_for_object(obj))):
|
||||
if current:
|
||||
issues_diff = self.get_issues_diff(current, version)
|
||||
diff_list.append(issues_diff)
|
||||
|
||||
current = version
|
||||
|
||||
return diff_list
|
||||
|
||||
|
||||
class IssueSerializer(serializers.ModelSerializer):
|
||||
tags = PickleField()
|
||||
comment = serializers.SerializerMethodField('get_comment')
|
||||
history = serializers.SerializerMethodField('get_history')
|
||||
is_closed = serializers.Field(source='is_closed')
|
||||
|
||||
class Meta:
|
||||
model = Issue
|
||||
|
@ -112,7 +159,7 @@ class IssueSerializer(serializers.ModelSerializer):
|
|||
|
||||
for version in reversed(list(reversion.get_for_object(obj))):
|
||||
if current:
|
||||
issues_diff = self.get_issues_diff(version, current)
|
||||
issues_diff = self.get_issues_diff(current, version)
|
||||
diff_list.append(issues_diff)
|
||||
|
||||
current = version
|
||||
|
|
|
@ -5,6 +5,7 @@ from rest_framework.urlpatterns import format_suffix_patterns
|
|||
|
||||
from greenmine.scrum import api
|
||||
|
||||
|
||||
urlpatterns = format_suffix_patterns(patterns('',
|
||||
url(r'^projects/$', api.ProjectList.as_view(), name='project-list'),
|
||||
url(r'^projects/(?P<pk>[0-9]+)/$', api.ProjectDetail.as_view(), name='project-detail'),
|
||||
|
@ -26,6 +27,8 @@ urlpatterns = format_suffix_patterns(patterns('',
|
|||
url(r'^issues/types/(?P<pk>[0-9]+)/$', api.IssueTypeDetail.as_view(), name='issues-type-detail'),
|
||||
url(r'^tasks/$', api.TaskList.as_view(), name='tasks-list'),
|
||||
url(r'^tasks/(?P<pk>[0-9]+)/$', api.TaskDetail.as_view(), name='tasks-detail'),
|
||||
url(r'^tasks/attachments/$', api.TasksAttachmentList.as_view(), name='tasks-attachment-list'),
|
||||
url(r'^tasks/attachments/(?P<pk>[0-9]+)/$', api.TasksAttachmentDetail.as_view(), name='tasks-attachment-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'^tasks/statuses/$', api.TaskStatusList.as_view(), name='tasks-status-list'),
|
||||
|
@ -33,4 +36,3 @@ urlpatterns = format_suffix_patterns(patterns('',
|
|||
url(r'^priorities/$', api.PriorityList.as_view(), name='priority-list'),
|
||||
url(r'^priorities/(?P<pk>[0-9]+)/$', api.PriorityDetail.as_view(), name='priority-detail'),
|
||||
))
|
||||
|
||||
|
|
|
@ -317,6 +317,9 @@ HAYSTACK_CONNECTIONS = {
|
|||
|
||||
HAYSTACK_DEFAULT_OPERATOR = 'AND'
|
||||
|
||||
MAX_SEARCH_RESULTS = 100
|
||||
|
||||
HAYSTACK_SIGNAL_PROCESSOR = 'haystack.signals.RealtimeSignalProcessor'
|
||||
|
||||
REST_FRAMEWORK = {
|
||||
'DEFAULT_AUTHENTICATION_CLASSES': (
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
from django.contrib import admin
|
||||
|
||||
from greenmine.wiki.models import WikiPage, WikiPageAttachment
|
||||
|
|
|
@ -1,3 +1,9 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
from django.core.exceptions import ImproperlyConfigured, ObjectDoesNotExist
|
||||
from django.utils.translation import ugettext as _
|
||||
from django.http import Http404
|
||||
|
||||
from rest_framework import generics
|
||||
|
||||
from greenmine.wiki.serializers import WikiPageSerializer, WikiPageAttachmentSerializer
|
||||
|
@ -21,6 +27,20 @@ class WikiPageDetail(generics.RetrieveUpdateDestroyAPIView):
|
|||
serializer_class = WikiPageSerializer
|
||||
permission_classes = (WikiPageDetailPermission,)
|
||||
|
||||
def get_object(self, queryset=None):
|
||||
if queryset is None:
|
||||
queryset = self.get_queryset()
|
||||
|
||||
queryset = queryset.filter(project=self.kwargs["projectid"],
|
||||
slug=self.kwargs["slug"])
|
||||
try:
|
||||
# Get the single item from the filtered queryset
|
||||
obj = queryset.get()
|
||||
except ObjectDoesNotExist:
|
||||
raise Http404(_("No {verbose_name} found matching the query").format(
|
||||
verbose_name=queryset.model._meta.verbose_name))
|
||||
return obj
|
||||
|
||||
|
||||
class WikiPageAttachmentList(generics.ListCreateAPIView):
|
||||
model = WikiPageAttachment
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
from django.db import models
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from greenmine.base.fields import DictField
|
||||
|
||||
|
||||
class WikiPage(models.Model):
|
||||
project = models.ForeignKey('scrum.Project', null=False, blank=False,
|
||||
|
@ -27,6 +27,8 @@ class WikiPage(models.Model):
|
|||
verbose_name = u'wiki page'
|
||||
verbose_name_plural = u'wiki pages'
|
||||
ordering = ['project', 'slug']
|
||||
unique_together = ('project', 'slug',)
|
||||
|
||||
permissions = (
|
||||
('can_view_wikipage', 'Can modify owned wiki pages'),
|
||||
('can_change_owned_wikipage', 'Can modify owned wiki pages'),
|
||||
|
@ -57,5 +59,5 @@ class WikiPageAttachment(models.Model):
|
|||
ordering = ['wikipage', 'created_date']
|
||||
|
||||
def __unicode__(self):
|
||||
return u'project {0} - page {1} - attachment {2}'.format(self.wikipage.project_id, self.wikipage.subject, self.id)
|
||||
|
||||
return u'project {0} - page {1} - attachment {2}'.format(self.wikipage.project_id,
|
||||
self.wikipage.subject, self.id)
|
||||
|
|
|
@ -1,5 +1,8 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
from greenmine.base.permissions import BaseDetailPermission
|
||||
|
||||
|
||||
class WikiPageDetailPermission(BaseDetailPermission):
|
||||
get_permission = "can_view_wikipage"
|
||||
put_permission = "change_wikipage"
|
||||
|
@ -8,6 +11,7 @@ class WikiPageDetailPermission(BaseDetailPermission):
|
|||
safe_methods = ['HEAD', 'OPTIONS']
|
||||
path_to_project = ['project']
|
||||
|
||||
|
||||
class WikiPageAttachmentDetailPermission(BaseDetailPermission):
|
||||
get_permission = "can_view_wikipageattachment"
|
||||
put_permission = "change_wikipageattachment"
|
||||
|
|
|
@ -1,13 +1,14 @@
|
|||
# -* coding: utf-8 -*-
|
||||
|
||||
from haystack import indexes
|
||||
from .models import WikiPage
|
||||
|
||||
|
||||
class WikiPageIndex(indexes.RealTimeSearchIndex, indexes.Indexable):
|
||||
class WikiPageIndex(indexes.SearchIndex, indexes.Indexable):
|
||||
text = indexes.CharField(document=True, use_template=True, template_name='search/indexes/wikipage_text.txt')
|
||||
|
||||
def get_model(self):
|
||||
return WikiPage
|
||||
|
||||
def index_queryset(self):
|
||||
def index_queryset(self, using=None):
|
||||
return self.get_model().objects.all()
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
from rest_framework import serializers
|
||||
|
||||
from greenmine.wiki.models import WikiPage, WikiPageAttachment
|
||||
|
|
|
@ -1,11 +1,14 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
from django.conf.urls import patterns, url
|
||||
from rest_framework.urlpatterns import format_suffix_patterns
|
||||
|
||||
from greenmine.wiki import api
|
||||
|
||||
|
||||
urlpatterns = format_suffix_patterns(patterns('',
|
||||
url(r'^wiki/pages/$', api.WikiPageList.as_view(), name='wiki-page-list'),
|
||||
url(r'^wiki/pages/(?P<slug>[\w\-\d]+)/$', api.WikiPageDetail.as_view(), name='wiki-page-detail'),
|
||||
url(r'^pages/$', api.WikiPageList.as_view(), name='wiki-page-list'),
|
||||
url(r'^pages/(?P<projectid>\d+)-(?P<slug>[\w\-\d]+)/$', api.WikiPageDetail.as_view(), name='wiki-page-detail'),
|
||||
#url(r'^wiki_page_attachments/$', api.WikiPageAttachmentList.as_view(), name='wiki-page-attachment-list'),
|
||||
#url(r'^wiki_page_attachments/(?P<pk>[0-9]+)/$', api.WikiPageAttachmentDetail.as_view(), name='wiki-page-attachment-detail'),
|
||||
))
|
||||
|
|
|
@ -19,3 +19,4 @@ six==1.3.0
|
|||
djangorestframework==2.2.5
|
||||
django-filter==0.6
|
||||
psycopg2==2.4.6
|
||||
django-sampledatahelper==0.0.1
|
||||
|
|
Loading…
Reference in New Issue