Initial REST api working, but without all resources
parent
5964845710
commit
561ac88e24
|
@ -0,0 +1,3 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,38 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
|
from django.http import HttpResponseRedirect, HttpResponse
|
||||||
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
from functools import wraps
|
||||||
|
import json
|
||||||
|
|
||||||
|
from superview.views import LazyEncoder
|
||||||
|
|
||||||
|
def login_required(view_func):
|
||||||
|
@wraps(view_func)
|
||||||
|
def _wrapper(self, request, *args, **kwargs):
|
||||||
|
if request.user.is_authenticated():
|
||||||
|
return view_func(self, request, *args, **kwargs)
|
||||||
|
|
||||||
|
if request.is_ajax():
|
||||||
|
response_dict = {'valid': False, 'errors':[_(u"Permission denied.")]}
|
||||||
|
response_data = json.dumps(response_dict, cls=LazyEncoder, indent=4, sort_keys=True)
|
||||||
|
return HttpResponse(response_data, mimetype='text/plain')
|
||||||
|
|
||||||
|
return HttpResponseRedirect(settings.LOGIN_URL)
|
||||||
|
return _wrapper
|
||||||
|
|
||||||
|
|
||||||
|
def staff_required(view_func):
|
||||||
|
@wraps(view_func)
|
||||||
|
def _wrapper(self, request, *args, **kwargs):
|
||||||
|
if request.user.is_staff:
|
||||||
|
return view_func(self, request, *args, **kwargs)
|
||||||
|
|
||||||
|
if request.is_ajax():
|
||||||
|
response_dict = {'valid': False, 'errors':[_(u"Permission denied.")]}
|
||||||
|
response_data = json.dumps(response_dict, cls=LazyEncoder, indent=4, sort_keys=True)
|
||||||
|
return HttpResponse(response_data, mimetype='text/plain')
|
||||||
|
|
||||||
|
return HttpResponseRedirect(settings.LOGIN_URL)
|
||||||
|
return _wrapper
|
|
@ -0,0 +1,130 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Copyright (c) 2011 Andrei Antoukh <niwi@niwi.be>
|
||||||
|
# License: BSD-3
|
||||||
|
|
||||||
|
from django.db import models
|
||||||
|
from base64 import b64encode, b64decode
|
||||||
|
|
||||||
|
try:
|
||||||
|
import cPickle as pickle
|
||||||
|
except ImportError:
|
||||||
|
import pickle
|
||||||
|
|
||||||
|
import logging
|
||||||
|
|
||||||
|
logger = logging.getLogger("niwi")
|
||||||
|
|
||||||
|
class DictField(models.Field):
|
||||||
|
""" Dictionary pickled field. """
|
||||||
|
__metaclass__ = models.SubfieldBase
|
||||||
|
__prefix__ = "pickle_dict::"
|
||||||
|
__pickleproto__ = -1
|
||||||
|
|
||||||
|
def to_python(self, value):
|
||||||
|
if isinstance(value, dict):
|
||||||
|
return value
|
||||||
|
if isinstance(value, (str, unicode)) and value.startswith(self.__prefix__):
|
||||||
|
local_value = value[len(self.__prefix__):]
|
||||||
|
return pickle.loads(b64decode(str(local_value)))
|
||||||
|
else:
|
||||||
|
return {}
|
||||||
|
|
||||||
|
def get_db_prep_value(self, value, connection, prepared=False):
|
||||||
|
if value is not None:
|
||||||
|
if isinstance(value, dict):
|
||||||
|
value = self.__prefix__ + b64encode(pickle.dumps(value, protocol=self.__pickleproto__))
|
||||||
|
else:
|
||||||
|
raise TypeError('This field can only store dictionaries.')
|
||||||
|
|
||||||
|
return value
|
||||||
|
|
||||||
|
def get_internal_type(self):
|
||||||
|
return 'TextField'
|
||||||
|
|
||||||
|
def value_to_string(self, obj):
|
||||||
|
if not obj:
|
||||||
|
return ""
|
||||||
|
|
||||||
|
value = getattr(obj, self.attname)
|
||||||
|
assert isinstance(value, dict)
|
||||||
|
return self.__prefix__ + b64encode(pickle.dumps(value, protocol=self.__pickleproto__))
|
||||||
|
return self.token.join(map(unicode, value))
|
||||||
|
|
||||||
|
def south_field_triple(self):
|
||||||
|
from south.modelsinspector import introspector
|
||||||
|
field_class = "django.db.models.fields.TextField"
|
||||||
|
args, kwargs = introspector(self)
|
||||||
|
return (field_class, args, kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
class ListField(models.Field):
|
||||||
|
""" Pickled list field. """
|
||||||
|
__metaclass__ = models.SubfieldBase
|
||||||
|
__prefix__ = "pickle_list::"
|
||||||
|
__pickleproto__ = -1
|
||||||
|
|
||||||
|
def to_python(self, value):
|
||||||
|
if isinstance(value, (list,tuple)):
|
||||||
|
return value
|
||||||
|
|
||||||
|
if isinstance(value, (str, unicode)) and value.startswith(self.__prefix__):
|
||||||
|
local_value = value[len(self.__prefix__):]
|
||||||
|
return pickle.loads(b64decode(str(local_value)))
|
||||||
|
else:
|
||||||
|
return []
|
||||||
|
|
||||||
|
def get_db_prep_value(self, value, connection, prepared=False):
|
||||||
|
if value is not None:
|
||||||
|
if isinstance(value, (list,tuple)):
|
||||||
|
value = self.__prefix__ + b64encode(pickle.dumps(value, protocol=self.__pickleproto__))
|
||||||
|
else:
|
||||||
|
raise TypeError('This field can only store list or tuple objects')
|
||||||
|
|
||||||
|
return value
|
||||||
|
|
||||||
|
def get_internal_type(self):
|
||||||
|
return 'TextField'
|
||||||
|
|
||||||
|
def value_to_string(self, obj):
|
||||||
|
if not obj:
|
||||||
|
return ""
|
||||||
|
|
||||||
|
value = getattr(obj, self.attname)
|
||||||
|
assert isinstance(value, (list, tuple))
|
||||||
|
return self.__prefix__ + b64encode(pickle.dumps(value, protocol=self.__pickleproto__))
|
||||||
|
return self.token.join(map(unicode, value))
|
||||||
|
|
||||||
|
def south_field_triple(self):
|
||||||
|
from south.modelsinspector import introspector
|
||||||
|
field_class = "django.db.models.fields.TextField"
|
||||||
|
args, kwargs = introspector(self)
|
||||||
|
return (field_class, args, kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
class CSVField(models.TextField):
|
||||||
|
__metaclass__ = models.SubfieldBase
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
self.token = kwargs.pop('token', ',')
|
||||||
|
super(CSVField, self).__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
def to_python(self, value):
|
||||||
|
if not value: return
|
||||||
|
if isinstance(value, list):
|
||||||
|
return value
|
||||||
|
return value.split(self.token)
|
||||||
|
|
||||||
|
def get_db_prep_value(self, value):
|
||||||
|
if not value: return
|
||||||
|
assert(isinstance(value, list) or isinstance(value, tuple))
|
||||||
|
return self.token.join([unicode(s) for s in value])
|
||||||
|
|
||||||
|
def value_to_string(self, obj):
|
||||||
|
value = self._get_val_from_obj(obj)
|
||||||
|
return self.get_db_prep_value(value)
|
||||||
|
|
||||||
|
def south_field_triple(self):
|
||||||
|
from south.modelsinspector import introspector
|
||||||
|
field_class = "django.db.models.fields.TextField"
|
||||||
|
args, kwargs = introspector(self)
|
||||||
|
return (field_class, args, kwargs)
|
|
@ -0,0 +1,17 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
DATE_FORMAT = "d/m/Y"
|
||||||
|
SHORT_DATE_FORMAT = "d/m/Y"
|
||||||
|
|
||||||
|
DATE_INPUT_FORMATS = (
|
||||||
|
'%Y-%m-%d', '%m/%d/%Y', '%d/%m/%Y', '%b %d %Y',
|
||||||
|
'%b %d, %Y', '%d %b %Y', '%d %b, %Y', '%B %d %Y',
|
||||||
|
'%B %d, %Y', '%d %B %Y', '%d %B, %Y'
|
||||||
|
)
|
||||||
|
|
||||||
|
DATETIME_INPUT_FORMATS = (
|
||||||
|
'%Y-%m-%d %H:%M:%S', '%Y-%m-%d %H:%M', '%Y-%m-%d',
|
||||||
|
'%m/%d/%Y %H:%M:%S', '%m/%d/%Y %H:%M', '%m/%d/%Y',
|
||||||
|
'%m/%d/%y %H:%M:%S', '%m/%d/%y %H:%M', '%m/%d/%y'
|
||||||
|
)
|
||||||
|
DECIMAL_SEPARATOR = '.'
|
|
@ -0,0 +1,15 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
DATE_FORMAT = "d/m/Y"
|
||||||
|
SHORT_DATE_FORMAT = "d/m/Y"
|
||||||
|
DATE_INPUT_FORMATS = (
|
||||||
|
'%Y-%m-%d', '%m/%d/%Y', '%d/%m/%Y', '%b %d %Y',
|
||||||
|
'%b %d, %Y', '%d %b %Y', '%d %b, %Y', '%B %d %Y',
|
||||||
|
'%B %d, %Y', '%d %B %Y', '%d %B, %Y'
|
||||||
|
)
|
||||||
|
DATETIME_INPUT_FORMATS = (
|
||||||
|
'%Y-%m-%d %H:%M:%S', '%Y-%m-%d %H:%M', '%Y-%m-%d',
|
||||||
|
'%m/%d/%Y %H:%M:%S', '%m/%d/%Y %H:%M', '%m/%d/%Y',
|
||||||
|
'%m/%d/%y %H:%M:%S', '%m/%d/%y %H:%M', '%m/%d/%y'
|
||||||
|
)
|
||||||
|
DECIMAL_SEPARATOR = '.'
|
|
@ -0,0 +1,46 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
from django.views.decorators.cache import cache_page
|
||||||
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
from django.utils.encoding import force_unicode
|
||||||
|
from django.http import HttpResponseRedirect, HttpResponse, Http404
|
||||||
|
from django.core.paginator import Paginator, InvalidPage, EmptyPage
|
||||||
|
from django.core.urlresolvers import reverse
|
||||||
|
from django.core.mail import EmailMessage
|
||||||
|
from django.shortcuts import render_to_response, get_object_or_404
|
||||||
|
from django.template.loader import render_to_string
|
||||||
|
from django.template import RequestContext, loader
|
||||||
|
from django.contrib import messages
|
||||||
|
from django.db.utils import IntegrityError
|
||||||
|
from django.utils.decorators import method_decorator
|
||||||
|
from superview.views import SuperView as View
|
||||||
|
|
||||||
|
from greenmine.core import permissions
|
||||||
|
from greenmine.core.middleware import PermissionDeniedException
|
||||||
|
|
||||||
|
|
||||||
|
class GenericView(View):
|
||||||
|
""" Generic view with some util methods. """
|
||||||
|
|
||||||
|
def render_to_ok(self, context={}):
|
||||||
|
response = {'valid': True, 'errors': []}
|
||||||
|
response.update(context)
|
||||||
|
return self.render_json(response, ok=True)
|
||||||
|
|
||||||
|
def render_to_error(self, context={}):
|
||||||
|
response = {'valid': False, 'errors': []}
|
||||||
|
response.update(context)
|
||||||
|
return self.render_json(response, ok=False)
|
||||||
|
|
||||||
|
def redirect_referer(self, msg=None):
|
||||||
|
if msg is not None:
|
||||||
|
messages.info(self.request, msg)
|
||||||
|
|
||||||
|
referer = self.request.META.get('HTTP_REFERER', '/')
|
||||||
|
return self.render_redirect(referer)
|
||||||
|
|
||||||
|
def check_role(self, user, project, perms, exception=PermissionDeniedException):
|
||||||
|
ok = permissions.has_perms(user, project, perms)
|
||||||
|
if exception is not None and not ok:
|
||||||
|
raise exception()
|
||||||
|
return ok
|
|
@ -0,0 +1,3 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,29 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from greenqueue.core import Library
|
||||||
|
register = Library()
|
||||||
|
|
||||||
|
from django.template import loader
|
||||||
|
from django.utils import translation
|
||||||
|
from django.core import mail
|
||||||
|
|
||||||
|
from greenmine.core.utils.auth import set_token
|
||||||
|
|
||||||
|
@register.task(name='send-mail')
|
||||||
|
def send_mail(subject, body, to):
|
||||||
|
email_message = mail.EmailMessage(body=body, subject=subject, to=to)
|
||||||
|
email_message.content_subtype = "html"
|
||||||
|
email_message.send()
|
||||||
|
|
||||||
|
@register.task(name='send-bulk-mail')
|
||||||
|
def send_bulk_mail(emails):
|
||||||
|
emessages = [mail.EmailMessage(body=body, subject=subject, to=to)
|
||||||
|
for subject, body, to in emails]
|
||||||
|
|
||||||
|
for msg in emessages:
|
||||||
|
msg.content_subtype = "html"
|
||||||
|
|
||||||
|
connection = mail.get_connection()
|
||||||
|
connection.send_messages(emessages)
|
|
@ -0,0 +1,13 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
from django.http import HttpResponse, HttpResponseForbidden
|
||||||
|
|
||||||
|
class PermissionDeniedException(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
class PermissionMiddleware(object):
|
||||||
|
def process_exception(self, request, exception):
|
||||||
|
if not isinstance(exception, PermissionDeniedException):
|
||||||
|
return None
|
||||||
|
|
||||||
|
return HttpResponseForbidden("Permission denied for %s" % (request.path))
|
|
@ -0,0 +1,65 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
from __future__ import absolute_import
|
||||||
|
|
||||||
|
# FIXME: move this file to base.
|
||||||
|
|
||||||
|
from greenmine.profile.models import Role
|
||||||
|
from greenmine.scrum.models import ProjectUserRole
|
||||||
|
|
||||||
|
def get_role(name):
|
||||||
|
"""
|
||||||
|
Helper method for a get role object
|
||||||
|
finding by name.
|
||||||
|
"""
|
||||||
|
return Role.objects.get(slug=name)
|
||||||
|
|
||||||
|
|
||||||
|
def has_perm(user, project, loc, perm, pur=None):
|
||||||
|
"""
|
||||||
|
Checks if a user has a concrete permission on
|
||||||
|
a project.
|
||||||
|
"""
|
||||||
|
|
||||||
|
if not pur:
|
||||||
|
try:
|
||||||
|
pur = ProjectUserRole.objects.get(project=project, user=user)
|
||||||
|
except ProjectUserRole.DoesNotExist:
|
||||||
|
return False
|
||||||
|
|
||||||
|
return getattr(pur.role, \
|
||||||
|
'%s_%s' % (loc.lower(), perm.lower()), False)
|
||||||
|
|
||||||
|
|
||||||
|
def has_perms(user, project, perms=[]):
|
||||||
|
"""
|
||||||
|
Check a group of permissions in a single call.
|
||||||
|
"""
|
||||||
|
|
||||||
|
if user.is_superuser:
|
||||||
|
return True
|
||||||
|
|
||||||
|
if project.owner == user:
|
||||||
|
return True
|
||||||
|
|
||||||
|
try:
|
||||||
|
pur, valid = ProjectUserRole.objects\
|
||||||
|
.get(project=project, user=user), True
|
||||||
|
except ProjectUserRole.DoesNotExist:
|
||||||
|
return False
|
||||||
|
|
||||||
|
for pitem in perms:
|
||||||
|
if len(pitem) != 2:
|
||||||
|
continue
|
||||||
|
|
||||||
|
loc, locperms = pitem
|
||||||
|
if not isinstance(locperms, (list, tuple)):
|
||||||
|
locperms = [locperms]
|
||||||
|
|
||||||
|
valid = False not in [has_perm(user, project, loc, locperm, pur=pur)\
|
||||||
|
for locperm in locperms]
|
||||||
|
|
||||||
|
if not valid:
|
||||||
|
break
|
||||||
|
|
||||||
|
return valid
|
|
@ -0,0 +1,27 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
import django.dispatch
|
||||||
|
|
||||||
|
mail_new_user = django.dispatch.Signal(providing_args=["user"])
|
||||||
|
mail_recovery_password = django.dispatch.Signal(providing_args=["user"])
|
||||||
|
|
||||||
|
mail_question_created = django.dispatch.Signal(providing_args=["question", "user"])
|
||||||
|
mail_question_assigned = django.dispatch.Signal(providing_args=["question", "user"])
|
||||||
|
mail_question_deleted = django.dispatch.Signal(providing_args=["question", "user"])
|
||||||
|
|
||||||
|
mail_project_created = django.dispatch.Signal(providing_args=["project", "user"])
|
||||||
|
mail_project_modified = django.dispatch.Signal(providing_args=["project", "user"])
|
||||||
|
mail_project_deleted = django.dispatch.Signal(providing_args=["project", "user"])
|
||||||
|
|
||||||
|
mail_milestone_created = django.dispatch.Signal(providing_args=["milestone", "user"])
|
||||||
|
mail_milestone_modified = django.dispatch.Signal(providing_args=["milestone", "user"])
|
||||||
|
mail_milestone_deleted = django.dispatch.Signal(providing_args=["milestone", "user"])
|
||||||
|
|
||||||
|
mail_userstory_created = django.dispatch.Signal(providing_args=["us", "user"])
|
||||||
|
mail_userstory_modified = django.dispatch.Signal(providing_args=["us", "user"])
|
||||||
|
mail_userstory_deleted = django.dispatch.Signal(providing_args=["us", "user"])
|
||||||
|
|
||||||
|
mail_task_created = django.dispatch.Signal(providing_args=["task", "user"])
|
||||||
|
mail_task_modified = django.dispatch.Signal(providing_args=["task", "user"])
|
||||||
|
mail_task_assigned = django.dispatch.Signal(providing_args=["task", "user"])
|
||||||
|
mail_task_deleted = django.dispatch.Signal(providing_args=["task", "user"])
|
|
@ -0,0 +1,40 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
import unicodedata
|
||||||
|
|
||||||
|
class Singleton(type):
|
||||||
|
""" Singleton metaclass. """
|
||||||
|
def __init__(cls, name, bases, dct):
|
||||||
|
cls.__instance = None
|
||||||
|
type.__init__(cls, name, bases, dct)
|
||||||
|
|
||||||
|
def __call__(cls, *args, **kw):
|
||||||
|
if cls.__instance is None:
|
||||||
|
cls.__instance = type.__call__(cls, *args,**kw)
|
||||||
|
return cls.__instance
|
||||||
|
|
||||||
|
|
||||||
|
def iter_points(queryset):
|
||||||
|
for item in queryset:
|
||||||
|
if item.points == -1:
|
||||||
|
yield 0
|
||||||
|
elif item.points == -2:
|
||||||
|
yield 0.5
|
||||||
|
else:
|
||||||
|
yield item.points
|
||||||
|
|
||||||
|
|
||||||
|
def clear_model_dict(data):
|
||||||
|
hidden_fields = ['password']
|
||||||
|
|
||||||
|
new_dict = {}
|
||||||
|
for key, val in data.items():
|
||||||
|
if not key.startswith('_') and key not in hidden_fields:
|
||||||
|
new_dict[key] = val
|
||||||
|
|
||||||
|
return new_dict
|
||||||
|
|
||||||
|
|
||||||
|
def normalize_tagname(tagname):
|
||||||
|
value = unicodedata.normalize('NFKD', tagname).encode('ascii', 'ignore')
|
||||||
|
return value.lower()
|
|
@ -0,0 +1,17 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
import uuid
|
||||||
|
|
||||||
|
def set_token(user):
|
||||||
|
"""
|
||||||
|
Set new token for user profile.
|
||||||
|
"""
|
||||||
|
|
||||||
|
token = unicode(uuid.uuid4())
|
||||||
|
profile = user.get_profile()
|
||||||
|
profile.token = token
|
||||||
|
|
||||||
|
user.save()
|
||||||
|
profile.save()
|
||||||
|
return token
|
||||||
|
|
|
@ -0,0 +1,33 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
import sys
|
||||||
|
import codecs
|
||||||
|
|
||||||
|
from docutils import nodes
|
||||||
|
from docutils.parsers.rst import Directive, directives
|
||||||
|
|
||||||
|
|
||||||
|
def set_source_info(directive, node):
|
||||||
|
node.source, node.line = \
|
||||||
|
directive.state_machine.get_source_and_line(directive.lineno)
|
||||||
|
|
||||||
|
|
||||||
|
class CodeBlock(Directive):
|
||||||
|
"""
|
||||||
|
Directive for a code block with special highlighting or line numbering
|
||||||
|
settings.
|
||||||
|
"""
|
||||||
|
|
||||||
|
has_content = True
|
||||||
|
required_arguments = 1
|
||||||
|
optional_arguments = 0
|
||||||
|
final_argument_whitespace = False
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
code = u'\n'.join(self.content)
|
||||||
|
|
||||||
|
literal = nodes.literal_block(code, code)
|
||||||
|
literal['classes'] = ['brush: java;']
|
||||||
|
|
||||||
|
set_source_info(self, literal)
|
||||||
|
return [literal]
|
|
@ -0,0 +1,40 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
from django.utils import baseconv
|
||||||
|
from django.template.defaultfilters import slugify
|
||||||
|
|
||||||
|
import time
|
||||||
|
|
||||||
|
def slugify_uniquely(value, model, slugfield="slug"):
|
||||||
|
"""
|
||||||
|
Returns a slug on a name which is unique within a model's table
|
||||||
|
"""
|
||||||
|
|
||||||
|
suffix = 0
|
||||||
|
potential = base = slugify(value)
|
||||||
|
if len(potential) == 0:
|
||||||
|
potential = 'null'
|
||||||
|
while True:
|
||||||
|
if suffix:
|
||||||
|
potential = "-".join([base, str(suffix)])
|
||||||
|
if not model.objects.filter(**{slugfield: potential}).count():
|
||||||
|
return potential
|
||||||
|
suffix += 1
|
||||||
|
|
||||||
|
|
||||||
|
def ref_uniquely(project, model, field='ref'):
|
||||||
|
"""
|
||||||
|
Returns a unique reference code based on base64 and time.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# this prevents concurrent and inconsistent references.
|
||||||
|
time.sleep(0.001)
|
||||||
|
|
||||||
|
new_timestamp = lambda: int("".join(str(time.time()).split(".")))
|
||||||
|
while True:
|
||||||
|
potential = baseconv.base62.encode(new_timestamp())
|
||||||
|
params = {field: potential, 'project': project}
|
||||||
|
if not model.objects.filter(**params).exists():
|
||||||
|
return potential
|
||||||
|
|
||||||
|
time.sleep(0.0002)
|
|
@ -4,7 +4,6 @@ from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
|
|
||||||
from greenmine.wiki.fields import WikiField
|
|
||||||
from greenmine.core.utils.slug import slugify_uniquely as slugify
|
from greenmine.core.utils.slug import slugify_uniquely as slugify
|
||||||
from greenmine.taggit.managers import TaggableManager
|
from greenmine.taggit.managers import TaggableManager
|
||||||
|
|
||||||
|
@ -12,7 +11,7 @@ from greenmine.taggit.managers import TaggableManager
|
||||||
class Document(models.Model):
|
class Document(models.Model):
|
||||||
title = models.CharField(max_length=150)
|
title = models.CharField(max_length=150)
|
||||||
slug = models.SlugField(unique=True, max_length=200, blank=True)
|
slug = models.SlugField(unique=True, max_length=200, blank=True)
|
||||||
description = WikiField(blank=True)
|
description = models.TextField(blank=True)
|
||||||
|
|
||||||
created_date = models.DateTimeField(auto_now_add=True)
|
created_date = models.DateTimeField(auto_now_add=True)
|
||||||
modified_date = models.DateTimeField(auto_now_add=True)
|
modified_date = models.DateTimeField(auto_now_add=True)
|
||||||
|
|
|
@ -11,7 +11,6 @@ from django.contrib.contenttypes.models import ContentType
|
||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
|
|
||||||
from greenmine.core.fields import DictField, ListField
|
from greenmine.core.fields import DictField, ListField
|
||||||
from greenmine.wiki.fields import WikiField
|
|
||||||
from greenmine.core.utils import iter_points
|
from greenmine.core.utils import iter_points
|
||||||
|
|
||||||
import datetime
|
import datetime
|
||||||
|
@ -20,7 +19,7 @@ import re
|
||||||
|
|
||||||
class Profile(models.Model):
|
class Profile(models.Model):
|
||||||
user = models.OneToOneField("auth.User", related_name='profile')
|
user = models.OneToOneField("auth.User", related_name='profile')
|
||||||
description = WikiField(blank=True)
|
description = models.TextField(blank=True)
|
||||||
photo = models.FileField(upload_to="files/msg",
|
photo = models.FileField(upload_to="files/msg",
|
||||||
max_length=500, null=True, blank=True)
|
max_length=500, null=True, blank=True)
|
||||||
|
|
||||||
|
|
|
@ -1,13 +1,12 @@
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from greenmine.core.utils.slug import slugify_uniquely
|
from greenmine.core.utils.slug import slugify_uniquely
|
||||||
from greenmine.wiki.fields import WikiField
|
|
||||||
from greenmine.taggit.managers import TaggableManager
|
from greenmine.taggit.managers import TaggableManager
|
||||||
|
|
||||||
|
|
||||||
class Question(models.Model):
|
class Question(models.Model):
|
||||||
subject = models.CharField(max_length=150)
|
subject = models.CharField(max_length=150)
|
||||||
slug = models.SlugField(unique=True, max_length=250, blank=True)
|
slug = models.SlugField(unique=True, max_length=250, blank=True)
|
||||||
content = WikiField(blank=True)
|
content = models.TextField(blank=True)
|
||||||
closed = models.BooleanField(default=False)
|
closed = models.BooleanField(default=False)
|
||||||
attached_file = models.FileField(upload_to="messages",
|
attached_file = models.FileField(upload_to="messages",
|
||||||
max_length=500, null=True, blank=True)
|
max_length=500, null=True, blank=True)
|
||||||
|
@ -53,7 +52,7 @@ class Question(models.Model):
|
||||||
|
|
||||||
|
|
||||||
class QuestionResponse(models.Model):
|
class QuestionResponse(models.Model):
|
||||||
content = WikiField()
|
content = models.TextField()
|
||||||
created_date = models.DateTimeField(auto_now_add=True)
|
created_date = models.DateTimeField(auto_now_add=True)
|
||||||
modified_date = models.DateTimeField(auto_now_add=True)
|
modified_date = models.DateTimeField(auto_now_add=True)
|
||||||
attached_file = models.FileField(upload_to="messages",
|
attached_file = models.FileField(upload_to="messages",
|
||||||
|
|
|
@ -0,0 +1,38 @@
|
||||||
|
# myapp/api.py
|
||||||
|
from tastypie.resources import ModelResource
|
||||||
|
from greenmine.scrum.models import *
|
||||||
|
|
||||||
|
class ProjectResource(ModelResource):
|
||||||
|
class Meta:
|
||||||
|
queryset = Project.objects.all()
|
||||||
|
resource_name = 'project'
|
||||||
|
|
||||||
|
class ProjectUserRoleResource(ModelResource):
|
||||||
|
class Meta:
|
||||||
|
queryset = ProjectUserRole.objects.all()
|
||||||
|
resource_name = 'projectuserrole'
|
||||||
|
|
||||||
|
class MilestoneResource(ModelResource):
|
||||||
|
class Meta:
|
||||||
|
queryset = Milestone.objects.all()
|
||||||
|
resource_name = 'milestone'
|
||||||
|
|
||||||
|
class UserStoryResource(ModelResource):
|
||||||
|
class Meta:
|
||||||
|
queryset = UserStory.objects.all()
|
||||||
|
resource_name = 'userstory'
|
||||||
|
|
||||||
|
class ChangeResource(ModelResource):
|
||||||
|
class Meta:
|
||||||
|
queryset = Change.objects.all()
|
||||||
|
resource_name = 'change'
|
||||||
|
|
||||||
|
class ChangeAttachmentResource(ModelResource):
|
||||||
|
class Meta:
|
||||||
|
queryset = ChangeAttachment.objects.all()
|
||||||
|
resource_name = 'changeattachment'
|
||||||
|
|
||||||
|
class TaskResource(ModelResource):
|
||||||
|
class Meta:
|
||||||
|
queryset = Task.objects.all()
|
||||||
|
resource_name = 'task'
|
|
@ -13,10 +13,9 @@ from django.contrib.auth.models import UserManager
|
||||||
|
|
||||||
from greenmine.core.utils.slug import slugify_uniquely, ref_uniquely
|
from greenmine.core.utils.slug import slugify_uniquely, ref_uniquely
|
||||||
from greenmine.core.fields import DictField, ListField
|
from greenmine.core.fields import DictField, ListField
|
||||||
from greenmine.wiki.fields import WikiField
|
|
||||||
from greenmine.core.utils import iter_points
|
from greenmine.core.utils import iter_points
|
||||||
from greenmine.taggit.managers import TaggableManager
|
from greenmine.taggit.managers import TaggableManager
|
||||||
import reversion
|
#import reversion
|
||||||
|
|
||||||
from .choices import *
|
from .choices import *
|
||||||
|
|
||||||
|
@ -60,7 +59,7 @@ class Project(models.Model):
|
||||||
uuid = models.CharField(max_length=40, unique=True, blank=True)
|
uuid = models.CharField(max_length=40, unique=True, blank=True)
|
||||||
name = models.CharField(max_length=250, unique=True)
|
name = models.CharField(max_length=250, unique=True)
|
||||||
slug = models.SlugField(max_length=250, unique=True, blank=True)
|
slug = models.SlugField(max_length=250, unique=True, blank=True)
|
||||||
description = WikiField(blank=False)
|
description = models.TextField(blank=False)
|
||||||
|
|
||||||
created_date = models.DateTimeField(auto_now_add=True)
|
created_date = models.DateTimeField(auto_now_add=True)
|
||||||
modified_date = models.DateTimeField(auto_now_add=True)
|
modified_date = models.DateTimeField(auto_now_add=True)
|
||||||
|
@ -429,7 +428,7 @@ class UserStory(models.Model):
|
||||||
tested = models.BooleanField(default=False)
|
tested = models.BooleanField(default=False)
|
||||||
|
|
||||||
subject = models.CharField(max_length=500)
|
subject = models.CharField(max_length=500)
|
||||||
description = WikiField()
|
description = models.TextField()
|
||||||
finish_date = models.DateTimeField(null=True, blank=True)
|
finish_date = models.DateTimeField(null=True, blank=True)
|
||||||
|
|
||||||
watchers = models.ManyToManyField('auth.User',
|
watchers = models.ManyToManyField('auth.User',
|
||||||
|
@ -683,7 +682,7 @@ class Task(models.Model):
|
||||||
choices=TASK_STATUS_CHOICES, null=True, blank=True)
|
choices=TASK_STATUS_CHOICES, null=True, blank=True)
|
||||||
|
|
||||||
subject = models.CharField(max_length=500)
|
subject = models.CharField(max_length=500)
|
||||||
description = WikiField(blank=True)
|
description = models.TextField(blank=True)
|
||||||
assigned_to = models.ForeignKey('auth.User',
|
assigned_to = models.ForeignKey('auth.User',
|
||||||
related_name='user_storys_assigned_to_me',
|
related_name='user_storys_assigned_to_me',
|
||||||
blank=True, null=True, default=None)
|
blank=True, null=True, default=None)
|
||||||
|
@ -797,13 +796,13 @@ class Task(models.Model):
|
||||||
return self_dict
|
return self_dict
|
||||||
|
|
||||||
|
|
||||||
reversion.register(ProjectExtras)
|
#reversion.register(ProjectExtras)
|
||||||
reversion.register(Project)
|
#reversion.register(Project)
|
||||||
reversion.register(ProjectUserRole)
|
#reversion.register(ProjectUserRole)
|
||||||
reversion.register(Milestone)
|
#reversion.register(Milestone)
|
||||||
reversion.register(UserStory)
|
#reversion.register(UserStory)
|
||||||
reversion.register(Change)
|
#reversion.register(Change)
|
||||||
reversion.register(ChangeAttachment)
|
#reversion.register(ChangeAttachment)
|
||||||
reversion.register(Task)
|
#reversion.register(Task)
|
||||||
|
|
||||||
from . import sigdispatch
|
from . import sigdispatch
|
||||||
|
|
|
@ -11,7 +11,6 @@ from greenmine.core.utils import normalize_tagname
|
||||||
from greenmine.core import signals
|
from greenmine.core import signals
|
||||||
from greenmine.core.utils.auth import set_token
|
from greenmine.core.utils.auth import set_token
|
||||||
|
|
||||||
from greenqueue import send_task
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.utils.translation import ugettext
|
from django.utils.translation import ugettext
|
||||||
from django.template.loader import render_to_string
|
from django.template.loader import render_to_string
|
||||||
|
@ -25,7 +24,8 @@ def mail_new_user(sender, user, **kwargs):
|
||||||
})
|
})
|
||||||
|
|
||||||
subject = ugettext("Greenmine: wellcome!")
|
subject = ugettext("Greenmine: wellcome!")
|
||||||
send_task("send-mail", args = [subject, template, [user.email]])
|
# TODO: convert to celery
|
||||||
|
#send_task("send-mail", args = [subject, template, [user.email]])
|
||||||
|
|
||||||
@receiver(signals.mail_recovery_password)
|
@receiver(signals.mail_recovery_password)
|
||||||
def mail_recovery_password(sender, user, **kwargs):
|
def mail_recovery_password(sender, user, **kwargs):
|
||||||
|
@ -35,7 +35,8 @@ def mail_recovery_password(sender, user, **kwargs):
|
||||||
"current_host": settings.HOST,
|
"current_host": settings.HOST,
|
||||||
})
|
})
|
||||||
subject = ugettext("Greenmine: password recovery.")
|
subject = ugettext("Greenmine: password recovery.")
|
||||||
send_task("send-mail", args = [subject, template, [user.email]])
|
# TODO: convert to celery
|
||||||
|
#send_task("send-mail", args = [subject, template, [user.email]])
|
||||||
|
|
||||||
|
|
||||||
@receiver(signals.mail_milestone_created)
|
@receiver(signals.mail_milestone_created)
|
||||||
|
@ -58,7 +59,8 @@ def mail_milestone_created(sender, milestone, user, **kwargs):
|
||||||
|
|
||||||
emails_list.append([subject, template, [person.email]])
|
emails_list.append([subject, template, [person.email]])
|
||||||
|
|
||||||
send_task("send-bulk-mail", args=[emails_list])
|
# TODO: convert to celery
|
||||||
|
#send_task("send-bulk-mail", args=[emails_list])
|
||||||
|
|
||||||
@receiver(signals.mail_userstory_created)
|
@receiver(signals.mail_userstory_created)
|
||||||
def mail_userstory_created(sender, us, user, **kwargs):
|
def mail_userstory_created(sender, us, user, **kwargs):
|
||||||
|
@ -81,7 +83,8 @@ def mail_userstory_created(sender, us, user, **kwargs):
|
||||||
|
|
||||||
emails_list.append([subject, template, [person.email]])
|
emails_list.append([subject, template, [person.email]])
|
||||||
|
|
||||||
send_task("send-bulk-mail", args=[emails_list])
|
# TODO: convert to celery
|
||||||
|
#send_task("send-bulk-mail", args=[emails_list])
|
||||||
|
|
||||||
|
|
||||||
@receiver(signals.mail_task_created)
|
@receiver(signals.mail_task_created)
|
||||||
|
@ -105,7 +108,8 @@ def mail_task_created(sender, task, user, **kwargs):
|
||||||
|
|
||||||
emails_list.append([subject, template, [person.email]])
|
emails_list.append([subject, template, [person.email]])
|
||||||
|
|
||||||
send_task("send-bulk-mail", args=[emails_list])
|
# TODO: convert to celery
|
||||||
|
#send_task("send-bulk-mail", args=[emails_list])
|
||||||
|
|
||||||
|
|
||||||
@receiver(signals.mail_task_assigned)
|
@receiver(signals.mail_task_assigned)
|
||||||
|
@ -118,4 +122,5 @@ def mail_task_assigned(sender, task, user, **kwargs):
|
||||||
})
|
})
|
||||||
|
|
||||||
subject = ugettext("Greenmine: task assigned")
|
subject = ugettext("Greenmine: task assigned")
|
||||||
send_task("send-mail", args = [subject, template, [task.assigned_to.email]])
|
# TODO: convert to celery
|
||||||
|
#send_task("send-mail", args = [subject, template, [task.assigned_to.email]])
|
||||||
|
|
|
@ -0,0 +1,8 @@
|
||||||
|
# -* coding: utf-8 -*-
|
||||||
|
from haystack import forms
|
||||||
|
|
||||||
|
|
||||||
|
class SearchForm(forms.SearchForm):
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
kwargs['load_all'] = True
|
||||||
|
super(SearchForm, self).__init__(*args, **kwargs)
|
|
@ -0,0 +1,54 @@
|
||||||
|
{% extends 'base.html' %}
|
||||||
|
{% load static from staticfiles %}
|
||||||
|
{% load i18n %}
|
||||||
|
{% load rawinclude greenmine_utils %}
|
||||||
|
|
||||||
|
{% block title %}
|
||||||
|
<span class="separator"> › </span>
|
||||||
|
<span class="title-item">{% trans "Search" %}</span>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block wrapper %}
|
||||||
|
<h1>{% trans "Search Results" %}</h1>
|
||||||
|
{% if page.object_list %}
|
||||||
|
<div id="search" class="search-ds list-container" >
|
||||||
|
<div class="middle-box">
|
||||||
|
<div class="list-header">
|
||||||
|
<div class="header-left">
|
||||||
|
<div class="header-item project left">{% trans "Project" %}</div>
|
||||||
|
<div class="header-item type left">{% trans "Type" %}</div>
|
||||||
|
<div class="header-item title">{% trans "Title" %}</div>
|
||||||
|
<div class="clearfix"></div>
|
||||||
|
</div>
|
||||||
|
<div class="clearfix"></div>
|
||||||
|
</div>
|
||||||
|
<div class="list-body">
|
||||||
|
{% for result in page.object_list %}
|
||||||
|
<div class="list-item">
|
||||||
|
<div class="body-left">
|
||||||
|
<div class="body-item project left">{{ result.object.project }}</div>
|
||||||
|
<div class="body-item type left">{{ result.verbose_name }}</div>
|
||||||
|
<div class="body-item title"><a href="{{ result.object.get_absolute_url }}">{{ result.object }}</a></div>
|
||||||
|
<div class="clearfix"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% if page.has_previous or page.has_next %}
|
||||||
|
<div>
|
||||||
|
{% if page.has_previous %}<a href="?q={{ query }}&page={{ page.previous_page_number }}">{% endif %}« {% trans "Previous" %}{% if page.has_previous %}</a>{% endif %}
|
||||||
|
|<span class="current">
|
||||||
|
{% blocktrans with number=page.number total=page.paginator.num_pages %}Page {{ number }} of {{ total }}{% endblocktrans %}</span>|
|
||||||
|
{% if page.has_next %}<a href="?q={{ query }}&page={{ page.next_page_number }}">{% endif %}{% trans "Next" %} »{% if page.has_next %}</a>{% endif %}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
{% else %}
|
||||||
|
<p>{% trans "No results found." %}</p>
|
||||||
|
{% endif %}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block top-headers %}
|
||||||
|
<link type="text/css" media="screen" rel="stylesheet" href="{% static 'css/search.css' %}" />
|
||||||
|
{% endblock %}
|
|
@ -0,0 +1,8 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from django.conf.urls.defaults import patterns, url
|
||||||
|
from .views import *
|
||||||
|
|
||||||
|
urlpatterns = patterns('',
|
||||||
|
url(r'^$', SearchView.as_view(), name='search'),
|
||||||
|
)
|
||||||
|
|
|
@ -0,0 +1,56 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from django.core.paginator import Paginator, InvalidPage
|
||||||
|
from django.conf import settings
|
||||||
|
from django.http import Http404
|
||||||
|
from haystack.query import EmptySearchQuerySet
|
||||||
|
|
||||||
|
from greenmine.core.decorators import login_required
|
||||||
|
from greenmine.core.generic import GenericView
|
||||||
|
from greenmine.scrum.models import Project
|
||||||
|
from .forms import SearchForm
|
||||||
|
|
||||||
|
|
||||||
|
SEARCH_RESULTS_PER_PAGE = getattr(settings, 'SEARCH_RESULTS_PER_PAGE', 20)
|
||||||
|
|
||||||
|
|
||||||
|
class SearchView(GenericView):
|
||||||
|
template_path = 'search-results.html'
|
||||||
|
|
||||||
|
@login_required
|
||||||
|
def get(self, request):
|
||||||
|
query = ''
|
||||||
|
results = EmptySearchQuerySet()
|
||||||
|
|
||||||
|
if request.user.is_staff:
|
||||||
|
projects = Project.objects.all()
|
||||||
|
else:
|
||||||
|
projects = request.user.projects.all() | \
|
||||||
|
request.user.projects_participant.all()
|
||||||
|
|
||||||
|
projects = projects.order_by('name').distinct()
|
||||||
|
|
||||||
|
if request.GET.get('q'):
|
||||||
|
form = SearchForm(request.GET)
|
||||||
|
|
||||||
|
if form.is_valid():
|
||||||
|
query = form.cleaned_data['q']
|
||||||
|
results = form.search().filter(project__in=projects)
|
||||||
|
else:
|
||||||
|
form = SearchForm()
|
||||||
|
|
||||||
|
paginator = Paginator(results, SEARCH_RESULTS_PER_PAGE)
|
||||||
|
|
||||||
|
try:
|
||||||
|
page = paginator.page(int(request.GET.get('page', 1)))
|
||||||
|
except InvalidPage:
|
||||||
|
raise Http404(_(u'No such page of results!'))
|
||||||
|
|
||||||
|
context = {
|
||||||
|
'form': form,
|
||||||
|
'page': page,
|
||||||
|
'paginator': paginator,
|
||||||
|
'query': query,
|
||||||
|
}
|
||||||
|
|
||||||
|
return self.render_to_response(self.template_path, context)
|
||||||
|
|
|
@ -100,14 +100,6 @@ EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
|
||||||
#EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
|
#EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
|
||||||
|
|
||||||
|
|
||||||
GREENQUEUE_BACKEND = 'greenqueue.backends.sync.SyncService'
|
|
||||||
GREENQUEUE_WORKER_MANAGER = 'greenqueue.worker.sync.SyncManager'
|
|
||||||
|
|
||||||
GREENQUEUE_TASK_MODULES = [
|
|
||||||
'greenmine.core.mail.async_tasks',
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
SV_CSS_MENU_ACTIVE = 'selected'
|
SV_CSS_MENU_ACTIVE = 'selected'
|
||||||
SV_CONTEXT_VARNAME = 'menu'
|
SV_CONTEXT_VARNAME = 'menu'
|
||||||
|
|
||||||
|
@ -183,7 +175,6 @@ TEMPLATE_CONTEXT_PROCESSORS = [
|
||||||
"django.core.context_processors.static",
|
"django.core.context_processors.static",
|
||||||
"django.core.context_processors.tz",
|
"django.core.context_processors.tz",
|
||||||
"django.contrib.messages.context_processors.messages",
|
"django.contrib.messages.context_processors.messages",
|
||||||
"greenmine.core.context.main",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
ROOT_URLCONF = 'greenmine.urls'
|
ROOT_URLCONF = 'greenmine.urls'
|
||||||
|
@ -198,6 +189,8 @@ INSTALLED_APPS = [
|
||||||
'django.contrib.contenttypes',
|
'django.contrib.contenttypes',
|
||||||
'django.contrib.sessions',
|
'django.contrib.sessions',
|
||||||
'django.contrib.messages',
|
'django.contrib.messages',
|
||||||
|
'grappelli',
|
||||||
|
'django.contrib.admin',
|
||||||
'django.contrib.staticfiles',
|
'django.contrib.staticfiles',
|
||||||
|
|
||||||
'greenmine.base',
|
'greenmine.base',
|
||||||
|
@ -209,11 +202,7 @@ INSTALLED_APPS = [
|
||||||
'greenmine.questions',
|
'greenmine.questions',
|
||||||
'greenmine.search',
|
'greenmine.search',
|
||||||
|
|
||||||
'django_gravatar',
|
|
||||||
'rawinclude',
|
|
||||||
'greenqueue',
|
|
||||||
'south',
|
'south',
|
||||||
'superview',
|
|
||||||
'haystack',
|
'haystack',
|
||||||
'reversion',
|
'reversion',
|
||||||
]
|
]
|
||||||
|
@ -289,11 +278,6 @@ LOGGING = {
|
||||||
'level':'INFO',
|
'level':'INFO',
|
||||||
'propagate': False,
|
'propagate': False,
|
||||||
},
|
},
|
||||||
'greenqueue': {
|
|
||||||
'handlers': ['console'],
|
|
||||||
'level': 'DEBUG',
|
|
||||||
'propagate': False,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -9,7 +9,6 @@ from django.db.models.related import RelatedObject
|
||||||
from django.utils.text import capfirst
|
from django.utils.text import capfirst
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
from .forms import TagField
|
|
||||||
from .models import TaggedItem, GenericTaggedItemBase, Tag
|
from .models import TaggedItem, GenericTaggedItemBase, Tag
|
||||||
from .utils import require_instance_manager
|
from .utils import require_instance_manager
|
||||||
|
|
||||||
|
@ -78,15 +77,6 @@ class TaggableManager(RelatedField):
|
||||||
def save_form_data(self, instance, value):
|
def save_form_data(self, instance, value):
|
||||||
getattr(instance, self.name).set(*value)
|
getattr(instance, self.name).set(*value)
|
||||||
|
|
||||||
def formfield(self, form_class=TagField, **kwargs):
|
|
||||||
defaults = {
|
|
||||||
"label": capfirst(self.verbose_name),
|
|
||||||
"help_text": self.help_text,
|
|
||||||
"required": not self.blank
|
|
||||||
}
|
|
||||||
defaults.update(kwargs)
|
|
||||||
return form_class(**defaults)
|
|
||||||
|
|
||||||
def value_from_object(self, instance):
|
def value_from_object(self, instance):
|
||||||
if instance.pk:
|
if instance.pk:
|
||||||
return self.through.objects.filter(**self.through.lookup_kwargs(instance))
|
return self.through.objects.filter(**self.through.lookup_kwargs(instance))
|
||||||
|
|
|
@ -3,10 +3,22 @@ from django.conf.urls import patterns, include, url
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
admin.autodiscover()
|
admin.autodiscover()
|
||||||
|
|
||||||
urlpatterns = patterns('',
|
from tastypie.api import Api
|
||||||
# Examples:
|
|
||||||
# url(r'^$', 'greenmine.views.home', name='home'),
|
|
||||||
# url(r'^greenmine/', include('greenmine.foo.urls')),
|
|
||||||
|
|
||||||
|
from scrum.api import *
|
||||||
|
|
||||||
|
v1_api = Api(api_name='v1')
|
||||||
|
v1_api.register(ProjectResource())
|
||||||
|
v1_api.register(ProjectUserRoleResource())
|
||||||
|
v1_api.register(MilestoneResource())
|
||||||
|
v1_api.register(UserStoryResource())
|
||||||
|
v1_api.register(ChangeResource())
|
||||||
|
v1_api.register(ChangeAttachmentResource())
|
||||||
|
v1_api.register(TaskResource())
|
||||||
|
|
||||||
|
urlpatterns = patterns('',
|
||||||
|
url(r'^api/', include(v1_api.urls)),
|
||||||
url(r'^admin/', include(admin.site.urls)),
|
url(r'^admin/', include(admin.site.urls)),
|
||||||
|
url(r'^grappelli/', include('grappelli.urls')),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -1,10 +1,9 @@
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from .fields import WikiField
|
|
||||||
|
|
||||||
class WikiPage(models.Model):
|
class WikiPage(models.Model):
|
||||||
project = models.ForeignKey('scrum.Project', related_name='wiki_pages')
|
project = models.ForeignKey('scrum.Project', related_name='wiki_pages')
|
||||||
slug = models.SlugField(max_length=500, db_index=True)
|
slug = models.SlugField(max_length=500, db_index=True)
|
||||||
content = WikiField(blank=False, null=True)
|
content = models.TextField(blank=False, null=True)
|
||||||
owner = models.ForeignKey("auth.User", related_name="wiki_pages", null=True)
|
owner = models.ForeignKey("auth.User", related_name="wiki_pages", null=True)
|
||||||
|
|
||||||
watchers = models.ManyToManyField('auth.User',
|
watchers = models.ManyToManyField('auth.User',
|
||||||
|
@ -41,7 +40,7 @@ class WikiPage(models.Model):
|
||||||
|
|
||||||
class WikiPageHistory(models.Model):
|
class WikiPageHistory(models.Model):
|
||||||
wikipage = models.ForeignKey("WikiPage", related_name="history_entries")
|
wikipage = models.ForeignKey("WikiPage", related_name="history_entries")
|
||||||
content = WikiField(blank=True, null=True)
|
content = models.TextField(blank=True, null=True)
|
||||||
created_date = models.DateTimeField()
|
created_date = models.DateTimeField()
|
||||||
owner = models.ForeignKey("auth.User", related_name="wiki_page_historys")
|
owner = models.ForeignKey("auth.User", related_name="wiki_page_historys")
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,9 @@
|
||||||
django
|
django
|
||||||
django-grappelli
|
django-grappelli
|
||||||
django-tastypie
|
django-tastypie
|
||||||
|
django-reversion
|
||||||
|
git+https://github.com/toastdriven/django-haystack.git@master#egg=django-haystack
|
||||||
|
django-celery
|
||||||
|
south
|
||||||
|
py-bcrypt
|
||||||
|
Whoosh
|
||||||
|
|
Loading…
Reference in New Issue