Initial REST api working, but without all resources

remotes/origin/enhancement/email-actions
Jesús Espino 2013-03-19 21:41:38 +01:00
parent 5964845710
commit 561ac88e24
35 changed files with 734 additions and 62 deletions

View File

@ -0,0 +1,3 @@
# -*- coding: utf-8 -*-

View File

@ -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

130
greenmine/core/fields.py Normal file
View File

@ -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)

View File

View File

View File

@ -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 = '.'

View File

View File

@ -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 = '.'

46
greenmine/core/generic.py Normal file
View File

@ -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

View File

@ -0,0 +1,3 @@
# -*- coding: utf-8 -*-

View File

@ -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)

View File

@ -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))

View File

@ -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

27
greenmine/core/signals.py Normal file
View File

@ -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"])

View File

@ -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()

View File

@ -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

View File

@ -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]

View File

@ -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)

View File

View File

@ -4,7 +4,6 @@ from django.utils.translation import ugettext_lazy as _
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.taggit.managers import TaggableManager
@ -12,7 +11,7 @@ from greenmine.taggit.managers import TaggableManager
class Document(models.Model):
title = models.CharField(max_length=150)
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)
modified_date = models.DateTimeField(auto_now_add=True)

View File

@ -11,7 +11,6 @@ from django.contrib.contenttypes.models import ContentType
from django.contrib.auth.models import User
from greenmine.core.fields import DictField, ListField
from greenmine.wiki.fields import WikiField
from greenmine.core.utils import iter_points
import datetime
@ -20,7 +19,7 @@ import re
class Profile(models.Model):
user = models.OneToOneField("auth.User", related_name='profile')
description = WikiField(blank=True)
description = models.TextField(blank=True)
photo = models.FileField(upload_to="files/msg",
max_length=500, null=True, blank=True)

View File

@ -1,13 +1,12 @@
from django.db import models
from greenmine.core.utils.slug import slugify_uniquely
from greenmine.wiki.fields import WikiField
from greenmine.taggit.managers import TaggableManager
class Question(models.Model):
subject = models.CharField(max_length=150)
slug = models.SlugField(unique=True, max_length=250, blank=True)
content = WikiField(blank=True)
content = models.TextField(blank=True)
closed = models.BooleanField(default=False)
attached_file = models.FileField(upload_to="messages",
max_length=500, null=True, blank=True)
@ -53,7 +52,7 @@ class Question(models.Model):
class QuestionResponse(models.Model):
content = WikiField()
content = models.TextField()
created_date = models.DateTimeField(auto_now_add=True)
modified_date = models.DateTimeField(auto_now_add=True)
attached_file = models.FileField(upload_to="messages",

38
greenmine/scrum/api.py Normal file
View File

@ -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'

View File

@ -13,10 +13,9 @@ from django.contrib.auth.models import UserManager
from greenmine.core.utils.slug import slugify_uniquely, ref_uniquely
from greenmine.core.fields import DictField, ListField
from greenmine.wiki.fields import WikiField
from greenmine.core.utils import iter_points
from greenmine.taggit.managers import TaggableManager
import reversion
#import reversion
from .choices import *
@ -60,7 +59,7 @@ class Project(models.Model):
uuid = models.CharField(max_length=40, unique=True, blank=True)
name = models.CharField(max_length=250, unique=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)
modified_date = models.DateTimeField(auto_now_add=True)
@ -429,7 +428,7 @@ class UserStory(models.Model):
tested = models.BooleanField(default=False)
subject = models.CharField(max_length=500)
description = WikiField()
description = models.TextField()
finish_date = models.DateTimeField(null=True, blank=True)
watchers = models.ManyToManyField('auth.User',
@ -683,7 +682,7 @@ class Task(models.Model):
choices=TASK_STATUS_CHOICES, null=True, blank=True)
subject = models.CharField(max_length=500)
description = WikiField(blank=True)
description = models.TextField(blank=True)
assigned_to = models.ForeignKey('auth.User',
related_name='user_storys_assigned_to_me',
blank=True, null=True, default=None)
@ -797,13 +796,13 @@ class Task(models.Model):
return self_dict
reversion.register(ProjectExtras)
reversion.register(Project)
reversion.register(ProjectUserRole)
reversion.register(Milestone)
reversion.register(UserStory)
reversion.register(Change)
reversion.register(ChangeAttachment)
reversion.register(Task)
#reversion.register(ProjectExtras)
#reversion.register(Project)
#reversion.register(ProjectUserRole)
#reversion.register(Milestone)
#reversion.register(UserStory)
#reversion.register(Change)
#reversion.register(ChangeAttachment)
#reversion.register(Task)
from . import sigdispatch

View File

@ -11,7 +11,6 @@ from greenmine.core.utils import normalize_tagname
from greenmine.core import signals
from greenmine.core.utils.auth import set_token
from greenqueue import send_task
from django.conf import settings
from django.utils.translation import ugettext
from django.template.loader import render_to_string
@ -25,7 +24,8 @@ def mail_new_user(sender, user, **kwargs):
})
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)
def mail_recovery_password(sender, user, **kwargs):
@ -35,7 +35,8 @@ def mail_recovery_password(sender, user, **kwargs):
"current_host": settings.HOST,
})
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)
@ -58,7 +59,8 @@ def mail_milestone_created(sender, milestone, user, **kwargs):
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)
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]])
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)
@ -105,7 +108,8 @@ def mail_task_created(sender, task, user, **kwargs):
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)
@ -118,4 +122,5 @@ def mail_task_assigned(sender, task, user, **kwargs):
})
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]])

View File

View File

@ -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)

View File

@ -0,0 +1,54 @@
{% extends 'base.html' %}
{% load static from staticfiles %}
{% load i18n %}
{% load rawinclude greenmine_utils %}
{% block title %}
<span class="separator"> &rsaquo; </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 }}&amp;page={{ page.previous_page_number }}">{% endif %}&laquo; {% 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 }}&amp;page={{ page.next_page_number }}">{% endif %}{% trans "Next" %} &raquo;{% 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 %}

8
greenmine/search/urls.py Normal file
View File

@ -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'),
)

56
greenmine/search/views.py Normal file
View File

@ -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)

View File

@ -100,14 +100,6 @@ EMAIL_BACKEND = 'django.core.mail.backends.console.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_CONTEXT_VARNAME = 'menu'
@ -183,7 +175,6 @@ TEMPLATE_CONTEXT_PROCESSORS = [
"django.core.context_processors.static",
"django.core.context_processors.tz",
"django.contrib.messages.context_processors.messages",
"greenmine.core.context.main",
]
ROOT_URLCONF = 'greenmine.urls'
@ -198,6 +189,8 @@ INSTALLED_APPS = [
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'grappelli',
'django.contrib.admin',
'django.contrib.staticfiles',
'greenmine.base',
@ -209,11 +202,7 @@ INSTALLED_APPS = [
'greenmine.questions',
'greenmine.search',
'django_gravatar',
'rawinclude',
'greenqueue',
'south',
'superview',
'haystack',
'reversion',
]
@ -289,11 +278,6 @@ LOGGING = {
'level':'INFO',
'propagate': False,
},
'greenqueue': {
'handlers': ['console'],
'level': 'DEBUG',
'propagate': False,
},
}
}

View File

@ -9,7 +9,6 @@ from django.db.models.related import RelatedObject
from django.utils.text import capfirst
from django.utils.translation import ugettext_lazy as _
from .forms import TagField
from .models import TaggedItem, GenericTaggedItemBase, Tag
from .utils import require_instance_manager
@ -78,15 +77,6 @@ class TaggableManager(RelatedField):
def save_form_data(self, instance, 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):
if instance.pk:
return self.through.objects.filter(**self.through.lookup_kwargs(instance))

View File

@ -3,10 +3,22 @@ from django.conf.urls import patterns, include, url
from django.contrib import admin
admin.autodiscover()
urlpatterns = patterns('',
# Examples:
# url(r'^$', 'greenmine.views.home', name='home'),
# url(r'^greenmine/', include('greenmine.foo.urls')),
from tastypie.api import Api
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'^grappelli/', include('grappelli.urls')),
)

View File

@ -1,10 +1,9 @@
from django.db import models
from .fields import WikiField
class WikiPage(models.Model):
project = models.ForeignKey('scrum.Project', related_name='wiki_pages')
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)
watchers = models.ManyToManyField('auth.User',
@ -41,7 +40,7 @@ class WikiPage(models.Model):
class WikiPageHistory(models.Model):
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()
owner = models.ForeignKey("auth.User", related_name="wiki_page_historys")

View File

@ -1,3 +1,9 @@
django
django-grappelli
django-tastypie
django-reversion
git+https://github.com/toastdriven/django-haystack.git@master#egg=django-haystack
django-celery
south
py-bcrypt
Whoosh