Initial sites & invitations implementation.

remotes/origin/enhancement/email-actions
Andrey Antukh 2013-12-02 13:23:16 +01:00
parent 98cd05c857
commit 6d0e03c98d
34 changed files with 1629 additions and 220 deletions

9
greenmine/base/admin.py Normal file
View File

@ -0,0 +1,9 @@
from django.contrib import admin
from .models import Site
class SiteAdmin(admin.ModelAdmin):
list_display = ('domain', 'name')
search_fields = ('domain', 'name')
admin.site.register(Site, SiteAdmin)

View File

@ -0,0 +1,12 @@
# -*- coding: utf-8 -*-
from rest_framework import viewsets
from rest_framework.response import Response
class SiteViewSet(viewsets.ViewSet):
def status(self, request, **kwargs):
return Response({})
sitestatus = SiteViewSet.as_view({"head": "status", "get": "status"})

View File

@ -2,21 +2,135 @@
from django.db.models.loading import get_model
from django.contrib.auth import logout, login, authenticate
from django.shortcuts import get_object_or_404
from rest_framework.response import Response
from rest_framework.permissions import AllowAny
from rest_framework import status, viewsets
from rest_framework.decorators import list_route
from greenmine.base.models import SiteMember
from greenmine.base.sites import get_active_site
from greenmine.base.users.models import User, Role
from greenmine.base.users.serializers import UserSerializer
from greenmine.base import exceptions as exc
from greenmine.base import auth
from greenmine.base.users.models import User, Role
from greenmine.base.users.serializers import UserSerializer
from .serializers import (PublicRegisterSerializer,
PrivateRegisterSerializer,
PrivateGenericRegisterSerializer,
PrivateRegisterExistingSerializer)
class AuthViewSet(viewsets.ViewSet):
permission_classes = (AllowAny,)
def _create_response(self, user):
serializer = UserSerializer(user)
response_data = serializer.data
response_data["auth_token"] = auth.get_token_for_user(user)
return response_data
def _create_site_member(self, user):
site = get_active_site()
if SiteMember.objects.filter(site=site, user=user).count() == 0:
site_member = SiteMember(site=site, user=user, email=user.email,
is_owner=False, is_staff=False)
site_member.save()
def _send_public_register_email(self, user):
context = {"user": user}
mbuilder = MagicMailBuilder()
email = mbuilder.public_register_user(user.email, context)
email.send()
def _public_register(self, request):
if not request.site.public_register:
raise exc.BadRequest("Public register is disabled for this site.")
serializer = PublicRegisterSerializer(data=request.DATA)
if not serializer.is_valid():
raise exc.BadRequest(serializer.errors)
data = serializer.data
user = User(username=data["username"],
first_name=data["first_name"],
last_name=data["last_name"],
email=data["email"])
user.set_password(data["password"])
user.save()
self._create_site_member(user)
#self._send_public_register_email(user)
response_data = self._create_response(user)
return Response(response_data, status=status.HTTP_201_CREATED)
def _send_private_register_email(self, user, **kwargs):
context = {"user": user}
context.update(kwargs)
mbuilder = MagicMailBuilder()
email = mbuilder.private_register_user(user.email, context)
email.send()
def _private_register(self, request):
base_serializer = PrivateGenericRegisterSerializer(data=request.DATA)
if not base_serializer.is_valid():
raise exc.BadRequest(base_serializer.errors)
membership_model = get_model("projects", "Membership")
try:
membership = membership_model.objects.get(token=base_serializer.data["token"])
except membership_model.DoesNotExist as e:
raise exc.BadRequest("Invalid token") from e
if base_serializer.data["existing"]:
serializer = PrivateRegisterExistingSerializer(data=request.DATA)
if not serializer.is_valid():
raise exc.BadRequest(serializer.errors)
user = get_object_or_404(User, username=serializer.data["username"])
if not user.check_password(serializer.data["password"]):
raise exc.BadRequest({"password": "Incorrect password"})
else:
serializer = PrivateRegisterSerializer(data=request.DATA)
if not serializer.is_valid():
raise exc.BadRequest(serializer.errors)
data = serializer.data
user = User(username=data["username"],
first_name=data["first_name"],
last_name=data["last_name"],
email=data["email"])
user.set_password(data["password"])
user.save()
self._create_site_member(user)
membership.user = user
membership.save()
#self._send_private_register_email(user, membership=membership)
response_data = self._create_response(user)
return Response(response_data, status=status.HTTP_201_CREATED)
@list_route(methods=["POST"], permission_classes=[AllowAny])
def register(self, request, **kwargs):
type = request.DATA.get("type", None)
if type == "public":
return self._public_register(request)
elif type == "private":
return self._private_register(request)
raise exc.BadRequest("invalid register type")
def create(self, request, **kwargs):
username = request.DATA.get('username', None)
password = request.DATA.get('password', None)
@ -29,7 +143,5 @@ class AuthViewSet(viewsets.ViewSet):
if not user.check_password(password):
raise exc.BadRequest("Invalid username or password")
serializer = UserSerializer(user)
response_data = serializer.data
response_data["auth_token"] = auth.get_token_for_user(user)
response_data = self._create_response(user)
return Response(response_data, status=status.HTTP_200_OK)

View File

@ -0,0 +1,29 @@
# -*- coding: utf-8 -*-
from rest_framework import serializers
class BaseRegisterSerializer(serializers.Serializer):
first_name = serializers.CharField(max_length=200)
last_name = serializers.CharField(max_length=200)
email = serializers.EmailField(max_length=200)
username = serializers.CharField(max_length=200)
password = serializers.CharField(min_length=4)
class PublicRegisterSerializer(BaseRegisterSerializer):
pass
class PrivateRegisterSerializer(BaseRegisterSerializer):
pass
class PrivateGenericRegisterSerializer(serializers.Serializer):
token = serializers.CharField(max_length=255, required=True)
existing = serializers.BooleanField()
# existing = serializers.ChoiceField(choices=[("on", "on"), ("off", "off")])
class PrivateRegisterExistingSerializer(serializers.Serializer):
username = serializers.CharField(max_length=200)
password = serializers.CharField(min_length=4)

View File

@ -1,19 +1,28 @@
# -*- coding: utf-8 -*-
import uuid
import json
from django.core.urlresolvers import reverse
from django.conf.urls import patterns, include, url
from django import test
from django.db.models import get_model
from rest_framework.views import APIView
from rest_framework import viewsets
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
from greenmine import urls
from greenmine.base import auth
from greenmine.base.users.tests import create_user
from greenmine.base.users.tests import create_user, create_site
from greenmine.projects.tests import create_project
from greenmine.base.models import Site, SiteMember
from greenmine.projects.models import Membership
class TestAuthView(APIView):
class TestAuthView(viewsets.ViewSet):
authentication_classes = (auth.Token,)
permission_classes = (IsAuthenticated,)
@ -22,11 +31,12 @@ class TestAuthView(APIView):
urls.urlpatterns += patterns("",
url(r'^test-api/v1/auth/', TestAuthView.as_view(), name="test-token-auth"),
url(r'^test-api/v1/auth/', TestAuthView.as_view({"get": "get"}), name="test-token-auth"),
)
class SimpleTokenAuthTests(test.TestCase):
class TokenAuthTests(test.TestCase):
fixtures = ["initial_site.json"]
def setUp(self):
self.user1 = create_user(1)
@ -41,3 +51,129 @@ class SimpleTokenAuthTests(test.TestCase):
self.assertEqual(response.status_code, 200)
self.assertEqual(response.content, b'"ok"')
class RegisterTests(test.TestCase):
def setUp(self):
self.user1 = create_user(1)
self.site1 = create_site("localhost1", True)
self.site2 = create_site("localhost2", False)
self.role = self._create_role()
self.project = create_project(1, self.user1)
def test_public_register_01(self):
data = {
"username": "pepe",
"password": "pepepepe",
"first_name": "pepe",
"last_name": "pepe",
"email": "pepe@pepe.com",
"type": "public",
}
url = reverse("auth-register")
response = self.client.post(url, data, HTTP_X_HOST=self.site1.name)
self.assertEqual(response.status_code, 201)
self.assertEqual(SiteMember.objects.filter(site=self.site1).count(), 1)
self.assertEqual(self.project.memberships.count(), 0)
def test_public_register_02(self):
data = {
"username": "pepe",
"password": "pepepepe",
"first_name": "pepe",
"last_name": "pepe",
"email": "pepe@pepe.com",
"type": "public",
}
url = reverse("auth-register")
response = self.client.post(url, data, HTTP_X_HOST=self.site2.name)
self.assertEqual(response.status_code, 400)
def test_private_register_01(self):
data = {
"username": "pepe",
"password": "pepepepe",
"first_name": "pepe",
"last_name": "pepe",
"email": "pepe@pepe.com",
"type": "private",
}
url = reverse("auth-register")
response = self.client.post(url, data, HTTP_X_HOST=self.site2.name)
self.assertEqual(response.status_code, 400)
def test_private_register_02(self):
membership = self._create_invitation("pepe@pepe.com")
data = {
"username": "pepe",
"password": "pepepepe",
"first_name": "pepe",
"last_name": "pepe",
"email": "pepe@pepe.com",
"type": "private",
"existing": False,
"token": membership.token,
}
self.assertEqual(self.project.memberships.exclude(user__isnull=True).count(), 0)
url = reverse("auth-register")
response = self.client.post(url, data=json.dumps(data),
content_type="application/json",
HTTP_X_HOST=self.site2.name)
self.assertEqual(response.status_code, 201)
self.assertEqual(self.project.memberships.exclude(user__isnull=True).count(), 1)
self.assertEqual(self.project.memberships.get().role, self.role)
self.assertEqual(SiteMember.objects.filter(site=self.site1).count(), 0)
self.assertEqual(SiteMember.objects.filter(site=self.site2).count(), 1)
def test_private_register_03(self):
membership = self._create_invitation("pepe@pepe.com")
data = {
"username": self.user1.username,
"password": self.user1.username,
"type": "private",
"existing": True,
"token": membership.token,
}
self.assertEqual(self.project.memberships.exclude(user__isnull=True).count(), 0)
url = reverse("auth-register")
response = self.client.post(url, data=json.dumps(data),
content_type="application/json",
HTTP_X_HOST=self.site2.name)
self.assertEqual(response.status_code, 201)
self.assertEqual(self.project.memberships.exclude(user__isnull=True).count(), 1)
self.assertEqual(self.project.memberships.get().role, self.role)
self.assertEqual(SiteMember.objects.filter(site=self.site1).count(), 0)
self.assertEqual(SiteMember.objects.filter(site=self.site2).count(), 1)
def _create_invitation(self, email):
token = str(uuid.uuid1())
membership_model = get_model("projects", "Membership")
instance = membership_model(project=self.project,
email=email,
role=self.role,
user=None,
token=token)
instance.save()
return instance
def _create_role(self):
role_model = get_model("users", "Role")
instance = role_model(name="foo", slug="foo",
order=1, computable=True)
instance.save()
return instance

View File

@ -2,6 +2,12 @@
from rest_framework import exceptions
from rest_framework import status
from rest_framework.response import Response
from django.core.exceptions import PermissionDenied as DjangoPermissionDenied
from django.http import Http404
from .utils.json import to_json
class BaseException(exceptions.APIException):
@ -66,3 +72,53 @@ class NotAuthenticated(exceptions.NotAuthenticated):
exception.
"""
pass
def format_exception(exc):
# TODO: this method need a refactor.
# TODO: should return in uniform way all exceptions.
if isinstance(exc.detail, (dict, list, tuple,)):
detail = exc.detail
else:
class_name = exc.__class__.__name__
class_module = exc.__class__.__module__
detail = {
"_error_message": exc.detail,
"_error_type": "{0}.{1}".format(class_module, class_name)
}
return detail
def exception_handler(exc):
"""
Returns the response that should be used for any given exception.
By default we handle the REST framework `APIException`, and also
Django's builtin `Http404` and `PermissionDenied` exceptions.
Any unhandled exceptions may return `None`, which will cause a 500 error
to be raised.
"""
if isinstance(exc, exceptions.APIException):
headers = {}
if getattr(exc, 'auth_header', None):
headers['WWW-Authenticate'] = exc.auth_header
if getattr(exc, 'wait', None):
headers['X-Throttle-Wait-Seconds'] = '%d' % exc.wait
detail = format_exception(exc)
return Response(detail, status=exc.status_code, headers=headers)
elif isinstance(exc, Http404):
return Response({'_error_message': 'Not found'},
status=status.HTTP_404_NOT_FOUND)
elif isinstance(exc, DjangoPermissionDenied):
return Response({'_error_message': 'Permission denied'},
status=status.HTTP_403_FORBIDDEN)
# Note: Unhandled exceptions will raise a 500 error.
return None

View File

@ -0,0 +1,10 @@
[
{
"model": "base.site",
"fields": {
"domain": "localhost",
"name": "localhost"
},
"pk": 1
}
]

View File

@ -1,32 +1,22 @@
import time
# -*- coding: utf-8 -*-
import json
from django.conf import settings
from django import http
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(SessionMiddleware):
def process_request(self, request):
engine = import_module(settings.SESSION_ENGINE)
session_key = request.META.get(settings.SESSION_HEADER_NAME, None)
if not session_key:
session_key = request.COOKIES.get(settings.SESSION_COOKIE_NAME, None)
request.session = engine.SessionStore(session_key)
from greenmine.base import sites
COORS_ALLOWED_ORIGINS = '*'
COORS_ALLOWED_METHODS = ['POST', 'GET', 'OPTIONS', 'PUT', 'DELETE', 'PATCH']
COORS_ALLOWED_HEADERS = ['Content-Type', 'X-Requested-With',
'Authorization', 'Accept-Encoding',
'X-Disable-Pagination']
COORS_ALLOWED_METHODS = ['POST', 'GET', 'OPTIONS', 'PUT', 'DELETE', 'PATCH', 'HEAD']
COORS_ALLOWED_HEADERS = ['content-type', 'x-requested-with',
'authorization', 'accept-encoding',
'x-disable-pagination', 'x-host']
COORS_ALLOWED_CREDENTIALS = True
COORS_EXPOSE_HEADERS = ["x-pagination-count", "x-paginated",
"x-paginated-by", "x-pagination-current"]
COORS_EXPOSE_HEADERS = ["x-pagination-count", "x-paginated", "x-paginated-by",
"x-paginated-by", "x-pagination-current", "x-site-host",
"x-site-register"]
from .exceptions import format_exception
class CoorsMiddleware(object):
@ -49,3 +39,28 @@ class CoorsMiddleware(object):
def process_response(self, request, response):
self._populate_response(response)
return response
class SitesMiddleware(object):
def process_request(self, request):
domain = request.META.get("HTTP_X_HOST", None)
if domain is not None:
try:
site = sites.get_site_for_domain(domain)
except sites.SiteNotFound as e:
detail = format_exception(e)
return http.HttpResponseBadRequest(json.dumps(detail))
else:
site = sites.get_default_site()
request.site = site
sites.activate(site)
def process_response(self, request, response):
sites.deactivate()
if hasattr(request, "site"):
response["X-Site-Host"] = request.site.domain
response["X-Site-Register"] = "on" if request.site.public_register else "off"
return response

View File

@ -0,0 +1,20 @@
# -*- coding: utf-8 -*-
import datetime
from south.db import db
from south.v2 import SchemaMigration
from django.db import models
class Migration(SchemaMigration):
def forwards(self, orm):
pass
def backwards(self, orm):
pass
models = {
}
complete_apps = ['base']

View File

@ -0,0 +1,111 @@
# -*- coding: utf-8 -*-
import datetime
from south.db import db
from south.v2 import SchemaMigration
from django.db import models
class Migration(SchemaMigration):
def forwards(self, orm):
# Adding model 'Site'
db.create_table('base_site', (
('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
('domain', self.gf('django.db.models.fields.CharField')(unique=True, max_length=255)),
('name', self.gf('django.db.models.fields.CharField')(max_length=255)),
('scheme', self.gf('django.db.models.fields.CharField')(max_length=60, default=None, null=True)),
('public_register', self.gf('django.db.models.fields.BooleanField')(default=False)),
))
db.send_create_signal('base', ['Site'])
# Adding model 'SiteMember'
db.create_table('base_sitemember', (
('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
('site', self.gf('django.db.models.fields.related.ForeignKey')(related_name='+', to=orm['base.Site'])),
('user', self.gf('django.db.models.fields.related.ForeignKey')(related_name='+', null=True, to=orm['users.User'])),
('email', self.gf('django.db.models.fields.EmailField')(max_length=255)),
('is_owner', self.gf('django.db.models.fields.BooleanField')(default=False)),
('is_staff', self.gf('django.db.models.fields.BooleanField')(default=False)),
))
db.send_create_signal('base', ['SiteMember'])
# Adding unique constraint on 'SiteMember', fields ['site', 'user']
db.create_unique('base_sitemember', ['site_id', 'user_id'])
def backwards(self, orm):
# Removing unique constraint on 'SiteMember', fields ['site', 'user']
db.delete_unique('base_sitemember', ['site_id', 'user_id'])
# Deleting model 'Site'
db.delete_table('base_site')
# Deleting model 'SiteMember'
db.delete_table('base_sitemember')
models = {
'auth.group': {
'Meta': {'object_name': 'Group'},
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'to': "orm['auth.Permission']", 'symmetrical': 'False'})
},
'auth.permission': {
'Meta': {'object_name': 'Permission', 'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)"},
'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
},
'base.site': {
'Meta': {'object_name': 'Site', 'ordering': "('domain',)"},
'domain': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'public_register': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'scheme': ('django.db.models.fields.CharField', [], {'max_length': '60', 'default': 'None', 'null': 'True'})
},
'base.sitemember': {
'Meta': {'object_name': 'SiteMember', 'ordering': "['email']", 'unique_together': "(('site', 'user'),)"},
'email': ('django.db.models.fields.EmailField', [], {'max_length': '255'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'is_owner': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'site': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'+'", 'to': "orm['base.Site']"}),
'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'+'", 'null': 'True', 'to': "orm['users.User']"})
},
'contenttypes.contenttype': {
'Meta': {'object_name': 'ContentType', 'db_table': "'django_content_type'", 'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)"},
'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
},
'users.user': {
'Meta': {'object_name': 'User', 'ordering': "['username']"},
'color': ('django.db.models.fields.CharField', [], {'blank': 'True', 'max_length': '9', 'default': "'#669933'"}),
'colorize_tags': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'default_language': ('django.db.models.fields.CharField', [], {'blank': 'True', 'max_length': '20', 'default': "''"}),
'default_timezone': ('django.db.models.fields.CharField', [], {'blank': 'True', 'max_length': '20', 'default': "''"}),
'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'email': ('django.db.models.fields.EmailField', [], {'blank': 'True', 'max_length': '75'}),
'first_name': ('django.db.models.fields.CharField', [], {'blank': 'True', 'max_length': '30'}),
'groups': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'to': "orm['auth.Group']", 'related_name': "'user_set'", 'symmetrical': 'False'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'last_name': ('django.db.models.fields.CharField', [], {'blank': 'True', 'max_length': '30'}),
'notify_changes_by_me': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'notify_level': ('django.db.models.fields.CharField', [], {'max_length': '32', 'default': "'all_owned_projects'"}),
'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
'photo': ('django.db.models.fields.files.FileField', [], {'blank': 'True', 'max_length': '500', 'null': 'True'}),
'token': ('django.db.models.fields.CharField', [], {'blank': 'True', 'max_length': '200', 'default': 'None', 'null': 'True'}),
'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'to': "orm['auth.Permission']", 'related_name': "'user_set'", 'symmetrical': 'False'}),
'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
}
}
complete_apps = ['base']

View File

@ -0,0 +1,95 @@
# -*- coding: utf-8 -*-
import datetime
from south.db import db
from south.v2 import DataMigration
from django.db import models
class Migration(DataMigration):
def forwards(self, orm):
"Write your forwards methods here."
# Note: Don't use "from appname.models import ModelName".
# Use orm.ModelName to refer to models in this application,
# and orm['appname.ModelName'] for models in other applications.
from django.core.management import call_command
call_command("loaddata", "initial_site.json")
site = orm["base.Site"].objects.get(pk=1)
for user in orm["users.User"].objects.all():
orm["base.SiteMember"].objects.create(user=user, site=site, email=user.email)
orm["base.SiteMember"].objects.filter(user_id=1).update(is_staff=True, is_owner=True)
def backwards(self, orm):
"Write your backwards methods here."
models = {
'auth.group': {
'Meta': {'object_name': 'Group'},
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'blank': 'True', 'to': "orm['auth.Permission']"})
},
'auth.permission': {
'Meta': {'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission', 'ordering': "('content_type__app_label', 'content_type__model', 'codename')"},
'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
},
'base.site': {
'Meta': {'object_name': 'Site', 'ordering': "('domain',)"},
'domain': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'public_register': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'scheme': ('django.db.models.fields.CharField', [], {'default': 'None', 'null': 'True', 'max_length': '60'})
},
'base.sitemember': {
'Meta': {'unique_together': "(('site', 'user'),)", 'object_name': 'SiteMember', 'ordering': "['email']"},
'email': ('django.db.models.fields.EmailField', [], {'max_length': '255'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'is_owner': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'site': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'+'", 'to': "orm['base.Site']"}),
'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'+'", 'to': "orm['users.User']", 'null': 'True'})
},
'contenttypes.contenttype': {
'Meta': {'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'", 'ordering': "('name',)"},
'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
},
'users.user': {
'Meta': {'object_name': 'User', 'ordering': "['username']"},
'color': ('django.db.models.fields.CharField', [], {'default': "'#669933'", 'blank': 'True', 'max_length': '9'}),
'colorize_tags': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'default_language': ('django.db.models.fields.CharField', [], {'default': "''", 'blank': 'True', 'max_length': '20'}),
'default_timezone': ('django.db.models.fields.CharField', [], {'default': "''", 'blank': 'True', 'max_length': '20'}),
'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'email': ('django.db.models.fields.EmailField', [], {'blank': 'True', 'max_length': '75'}),
'first_name': ('django.db.models.fields.CharField', [], {'blank': 'True', 'max_length': '30'}),
'groups': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'symmetrical': 'False', 'related_name': "'user_set'", 'to': "orm['auth.Group']"}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'last_name': ('django.db.models.fields.CharField', [], {'blank': 'True', 'max_length': '30'}),
'notify_changes_by_me': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'notify_level': ('django.db.models.fields.CharField', [], {'default': "'all_owned_projects'", 'max_length': '32'}),
'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
'photo': ('django.db.models.fields.files.FileField', [], {'blank': 'True', 'null': 'True', 'max_length': '500'}),
'token': ('django.db.models.fields.CharField', [], {'default': 'None', 'blank': 'True', 'null': 'True', 'max_length': '200'}),
'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'symmetrical': 'False', 'related_name': "'user_set'", 'to': "orm['auth.Permission']"}),
'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
}
}
complete_apps = ['base']
symmetrical = True

View File

View File

@ -1,8 +1,74 @@
# -*- coding: utf-8 -*-
import string
from django.db import models
from django.db.models.signals import pre_save, pre_delete
from django.utils.translation import ugettext_lazy as _
from django.core.exceptions import ValidationError
from . import sites
def _simple_domain_name_validator(value):
"""
Validates that the given value contains no whitespaces to prevent common
typos.
"""
if not value:
return
checks = ((s in value) for s in string.whitespace)
if any(checks):
raise ValidationError(
_("The domain name cannot contain any spaces or tabs."),
code='invalid',
)
class Site(models.Model):
domain = models.CharField(_('domain name'), max_length=255, unique=True,
validators=[_simple_domain_name_validator])
name = models.CharField(_('display name'), max_length=255)
scheme = models.CharField(_('scheme'), max_length=60, null=True, default=None)
# Site Metadata
public_register = models.BooleanField(default=False)
class Meta:
verbose_name = _('site')
verbose_name_plural = _('sites')
ordering = ('domain',)
def __str__(self):
return self.domain
class SiteMember(models.Model):
site = models.ForeignKey("Site", related_name="+")
user = models.ForeignKey("users.User", related_name="+", null=True)
email = models.EmailField(max_length=255)
is_owner = models.BooleanField(default=False)
is_staff = models.BooleanField(default=False)
class Meta:
ordering = ["email"]
verbose_name = "Site Member"
verbose_name_plural = "Site Members"
unique_together = ("site", "user")
def __str__(self):
return "SiteMember: {0}:{1}".format(self.site, self.user)
pre_save.connect(sites.clear_site_cache, sender=Site)
pre_delete.connect(sites.clear_site_cache, sender=Site)
# Patch api view for correctly return 401 responses on
# request is authenticated instead of 403
from . import monkey
monkey.patch_api_view()
monkey.patch_serializer()
monkey.patch_import_module()
monkey.patch_south_hacks()

View File

@ -66,3 +66,18 @@ def patch_import_module():
import importlib
django_importlib.import_module = importlib.import_module
def patch_south_hacks():
from south.hacks import django_1_0
orig_set_installed_apps = django_1_0.Hacks.set_installed_apps
def set_installed_apps(self, apps, preserve_models=True):
return orig_set_installed_apps(self, apps, preserve_models=preserve_models)
orig__redo_app_cache = django_1_0.Hacks._redo_app_cache
def _redo_app_cache(self, preserve_models=True):
return orig__redo_app_cache(self, preserve_models=preserve_models)
django_1_0.Hacks.set_installed_apps = set_installed_apps
django_1_0.Hacks._redo_app_cache = _redo_app_cache

82
greenmine/base/sites.py Normal file
View File

@ -0,0 +1,82 @@
# -*- coding: utf-8 -*-
import logging
from threading import local
from django.db.models import get_model
from django.core.exceptions import ImproperlyConfigured
from . import exceptions as exc
_local = local()
log = logging.getLogger("greenmine.sites")
class SiteNotFound(exc.BaseException):
pass
def get_default_site():
from django.conf import settings
try:
sid = settings.SITE_ID
except AttributeError:
raise ImproperlyConfigured("You're using the \"sites framework\" without having "
"set the SITE_ID setting. Create a site in your database "
"and set the SITE_ID setting to fix this error.")
model_cls = get_model("base", "Site")
cached = getattr(_local, "default_site", None)
if cached is None:
try:
cached = _local.default_site = model_cls.objects.get(pk=sid)
except model_cls.DoesNotExist:
raise ImproperlyConfigured("default site not found on database.")
return cached
def get_site_for_domain(domain):
log.debug("Trying activate site for domain: {}".format(domain))
cache = getattr(_local, "cache", {})
if domain in cache:
return cache[domain]
model_cls = get_model("base", "Site")
try:
site = model_cls.objects.get(domain=domain)
except model_cls.DoesNotExist:
log.warning("Site does not exist for domain: {}".format(domain))
raise SiteNotFound("site not found")
else:
cache[domain] = site
return site
def activate(site):
log.debug("Activating site: {}".format(site))
_local.active_site = site
def deactivate():
if hasattr(_local, "active_site"):
log.debug("Deactivating site: {}".format(_local.active_site))
del _local.active_site
def get_active_site():
active_site = getattr(_local, "active_site", None)
if active_site is None:
return get_default_site()
return active_site
def clear_site_cache(**kwargs):
if hasattr(_local, "default_site"):
del _local.default_site
if hasattr(_local, "cache"):
del _local.cache

View File

@ -15,55 +15,17 @@ from djmail.template_mail import MagicMailBuilder
from greenmine.base import exceptions as exc
from greenmine.base.filters import FilterBackend
from greenmine.base.api import ModelCrudViewSet
from greenmine.base.api import ModelCrudViewSet, RetrieveModelMixin
from .models import User, Role
from .serializers import UserSerializer, RoleSerializer, RecoverySerializer
class RolesViewSet(viewsets.ViewSet):
permission_classes = (IsAuthenticated,)
serializer_class = RoleSerializer
def list(self, request, pk=None):
queryset = Role.objects.all()
serializer = self.serializer_class(queryset, many=True)
return Response(serializer.data)
def retrieve(self, request, pk=None):
try:
role = Role.objects.get(pk=pk)
except Role.DoesNotExist:
raise exc.NotFound()
serializer = self.serializer_class(role)
return Response(serializer.data)
class ProjectMembershipFilter(FilterBackend):
def filter_queryset(self, request, queryset, view):
queryset = super().filter_queryset(request, queryset, view)
if request.user.is_superuser:
return queryset
project_model = get_model("projects", "Project")
own_projects = project_model.objects.filter(members=request.user)
project = request.QUERY_PARAMS.get('project', None)
if project is not None:
own_projects = own_projects.filter(pk=project)
queryset = (queryset.filter(projects__in=own_projects)
.order_by('username').distinct())
return queryset
class UsersViewSet(ModelCrudViewSet):
permission_classes = (IsAuthenticated,)
serializer_class = UserSerializer
queryset = User.objects.all()
filter_backends = (ProjectMembershipFilter,)
filter_fields = [("project", "memberships__project__pk")]
def pre_conditions_on_save(self, obj):
@ -129,3 +91,22 @@ class UsersViewSet(ModelCrudViewSet):
request.user.set_password(password)
request.user.save(update_fields=["password"])
return Response(status=status.HTTP_204_NO_CONTENT)
class RolesViewSet(viewsets.ViewSet):
permission_classes = (IsAuthenticated,)
serializer_class = RoleSerializer
def list(self, request, pk=None):
queryset = Role.objects.all()
serializer = self.serializer_class(queryset, many=True)
return Response(serializer.data)
def retrieve(self, request, pk=None):
try:
role = Role.objects.get(pk=pk)
except Role.DoesNotExist:
raise exc.NotFound()
serializer = self.serializer_class(role)
return Response(serializer.data)

View File

@ -21,3 +21,14 @@ def create_user(id, save=True, is_superuser=False):
if save:
instance.save()
return instance
def create_site(name, public_register=False):
site_model = get_model("base", "Site")
instance = site_model(name=name,
domain=name,
public_register=public_register)
instance.save()
return instance

View File

@ -0,0 +1,12 @@
# -*- coding: utf-8 -*-
import json
from rest_framework.utils import encoders
def to_json(data, ensure_ascii=True, encoder_class=encoders.JSONEncoder):
return json.dumps(data, cls=encoder_class, indent=None, ensure_ascii=ensure_ascii)
def from_json(data):
return json.loads(data)

View File

@ -1,10 +1,10 @@
# -*- coding: utf-8 -*-
from django.conf import settings
import django_sites as sites
from django_jinja import library
from greenmine.base import sites
URLS = {
"home": "/",
@ -15,20 +15,16 @@ URLS = {
"issue": "/#/project/{0}/issues/{1}",
"project-admin": "/#/project/{0}/admin",
"change-password": "/#/change-password/{0}",
"invitation": "/#/invitation/{0}",
}
lib = library.Library()
def get_current_site():
current_site_id = getattr(settings, "SITE_ID")
front_sites = getattr(settings, "SITES_FRONT")
return sites.Site(front_sites[current_site_id])
@lib.global_function(name="resolve_front_url")
def resolve(type, *args):
site = get_current_site()
site = sites.get_active_site()
url_tmpl = "{scheme}//{domain}{url}"
scheme = site.scheme and "{0}:".format(site.scheme) or ""

View File

@ -1,13 +1,20 @@
# -*- coding: utf-8 -*-
import uuid
from django.db.models import Q
from rest_framework.permissions import IsAuthenticated
from rest_framework.permissions import IsAuthenticated, AllowAny
from rest_framework.response import Response
from rest_framework.decorators import detail_route
from rest_framework import viewsets
from rest_framework import status
from djmail.template_mail import MagicMailBuilder
from greenmine.base import filters
from greenmine.base.api import ModelCrudViewSet, ModelListViewSet
from greenmine.base import exceptions as exc
from greenmine.base.api import ModelCrudViewSet, ModelListViewSet, RetrieveModelMixin
from greenmine.base.notifications.api import NotificationSenderMixin
from greenmine.projects.aggregates.tags import get_all_tags
@ -53,6 +60,12 @@ class ProjectViewSet(ModelCrudViewSet):
def pre_save(self, obj):
obj.owner = self.request.user
# Assign site only if it current
# value is None
if not obj.site:
obj.site = self.request.site
super().pre_save(obj)
@ -61,6 +74,54 @@ class MembershipViewSet(ModelCrudViewSet):
serializer_class = serializers.MembershipSerializer
permission_classes = (IsAuthenticated, permissions.MembershipPermission)
def create(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.DATA, files=request.FILES)
if serializer.is_valid():
qs = self.model.objects.filter(Q(project_id=serializer.data["project"],
user__email=serializer.data["email"]) |
Q(project_id=serializer.data["project"],
email=serializer.data["email"]))
if qs.count() > 0:
raise exc.WrongArguments("Already exist user with specified email address.")
self.pre_save(serializer.object)
self.object = serializer.save(force_insert=True)
self.post_save(self.object, created=True)
headers = self.get_success_headers(serializer.data)
return Response(serializer.data, status=status.HTTP_201_CREATED,
headers=headers)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
def pre_save(self, object):
# Only assign new token if a current token value is empty.
if not object.token:
object.token = str(uuid.uuid1())
super().pre_save(object)
def post_save(self, object, created=False):
super().post_save(object, created=created)
if not created:
return
# Send email only if a new membership is created
mbuilder = MagicMailBuilder()
email = mbuilder.membership_invitation(object.email, {"membership": object})
email.send()
class InvitationViewSet(RetrieveModelMixin, viewsets.GenericViewSet):
"""
Only used by front for get invitation by it token.
"""
queryset = models.Membership.objects.all()
serializer_class = serializers.MembershipSerializer
lookup_field = "token"
permission_classes = (AllowAny,)
# User Stories commin ViewSets

View File

@ -0,0 +1,242 @@
# -*- coding: utf-8 -*-
import datetime
from south.db import db
from south.v2 import SchemaMigration
from django.db import models
class Migration(SchemaMigration):
def forwards(self, orm):
# Removing unique constraint on 'Membership', fields ['user', 'project']
db.delete_unique('projects_membership', ['user_id', 'project_id'])
# Adding field 'Project.site'
db.add_column('projects_project', 'site',
self.gf('django.db.models.fields.related.ForeignKey')(default=None, related_name='projects', null=True, to=orm['base.Site']),
keep_default=False)
# Adding field 'Membership.email'
db.add_column('projects_membership', 'email',
self.gf('django.db.models.fields.EmailField')(max_length=255, default=None, null=True),
keep_default=False)
# Adding field 'Membership.created_at'
db.add_column('projects_membership', 'created_at',
self.gf('django.db.models.fields.DateTimeField')(default=datetime.datetime.now, blank=True, auto_now_add=True),
keep_default=False)
# Adding field 'Membership.token'
db.add_column('projects_membership', 'token',
self.gf('django.db.models.fields.CharField')(max_length=60, default=None, blank=True, null=True, unique=True),
keep_default=False)
# Changing field 'Membership.user'
db.alter_column('projects_membership', 'user_id', self.gf('django.db.models.fields.related.ForeignKey')(null=True, to=orm['users.User']))
def backwards(self, orm):
# Deleting field 'Project.site'
db.delete_column('projects_project', 'site_id')
# Deleting field 'Membership.email'
db.delete_column('projects_membership', 'email')
# Deleting field 'Membership.created_at'
db.delete_column('projects_membership', 'created_at')
# Deleting field 'Membership.token'
db.delete_column('projects_membership', 'token')
# User chose to not deal with backwards NULL issues for 'Membership.user'
raise RuntimeError("Cannot reverse this migration. 'Membership.user' and its values cannot be restored.")
# The following code is provided here to aid in writing a correct migration
# Changing field 'Membership.user'
db.alter_column('projects_membership', 'user_id', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['users.User']))
# Adding unique constraint on 'Membership', fields ['user', 'project']
db.create_unique('projects_membership', ['user_id', 'project_id'])
models = {
'auth.group': {
'Meta': {'object_name': 'Group'},
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '80', 'unique': 'True'}),
'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'blank': 'True', 'to': "orm['auth.Permission']"})
},
'auth.permission': {
'Meta': {'object_name': 'Permission', 'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)"},
'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
},
'base.site': {
'Meta': {'object_name': 'Site', 'ordering': "('domain',)"},
'domain': ('django.db.models.fields.CharField', [], {'max_length': '255', 'unique': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'public_register': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'scheme': ('django.db.models.fields.CharField', [], {'max_length': '60', 'default': 'None', 'null': 'True'})
},
'contenttypes.contenttype': {
'Meta': {'object_name': 'ContentType', 'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'db_table': "'django_content_type'"},
'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
},
'projects.attachment': {
'Meta': {'object_name': 'Attachment', 'ordering': "['project', 'created_date']"},
'attached_file': ('django.db.models.fields.files.FileField', [], {'max_length': '500', 'null': 'True', 'blank': 'True'}),
'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
'created_date': ('django.db.models.fields.DateTimeField', [], {'blank': 'True', 'auto_now_add': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'modified_date': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
'object_id': ('django.db.models.fields.PositiveIntegerField', [], {}),
'owner': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'change_attachments'", 'to': "orm['users.User']"}),
'project': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'attachments'", 'to': "orm['projects.Project']"})
},
'projects.issuestatus': {
'Meta': {'object_name': 'IssueStatus', 'ordering': "['project', 'order', 'name']", 'unique_together': "(('project', 'name'),)"},
'color': ('django.db.models.fields.CharField', [], {'max_length': '20', 'default': "'#999999'"}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'is_closed': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'order': ('django.db.models.fields.IntegerField', [], {'default': '10'}),
'project': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'issue_statuses'", 'to': "orm['projects.Project']"})
},
'projects.issuetype': {
'Meta': {'object_name': 'IssueType', 'ordering': "['project', 'order', 'name']", 'unique_together': "(('project', 'name'),)"},
'color': ('django.db.models.fields.CharField', [], {'max_length': '20', 'default': "'#999999'"}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'order': ('django.db.models.fields.IntegerField', [], {'default': '10'}),
'project': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'issue_types'", 'to': "orm['projects.Project']"})
},
'projects.membership': {
'Meta': {'object_name': 'Membership', 'ordering': "['project', 'role']"},
'created_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now', 'blank': 'True', 'auto_now_add': 'True'}),
'email': ('django.db.models.fields.EmailField', [], {'max_length': '255', 'default': 'None', 'null': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'project': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'memberships'", 'to': "orm['projects.Project']"}),
'role': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'memberships'", 'to': "orm['users.Role']"}),
'token': ('django.db.models.fields.CharField', [], {'max_length': '60', 'default': 'None', 'blank': 'True', 'null': 'True', 'unique': 'True'}),
'user': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': "'memberships'", 'null': 'True', 'to': "orm['users.User']", 'blank': 'True'})
},
'projects.points': {
'Meta': {'object_name': 'Points', 'ordering': "['project', 'order', 'name']", 'unique_together': "(('project', 'name'),)"},
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'order': ('django.db.models.fields.IntegerField', [], {'default': '10'}),
'project': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'points'", 'to': "orm['projects.Project']"}),
'value': ('django.db.models.fields.FloatField', [], {'default': 'None', 'null': 'True', 'blank': 'True'})
},
'projects.priority': {
'Meta': {'object_name': 'Priority', 'ordering': "['project', 'order', 'name']", 'unique_together': "(('project', 'name'),)"},
'color': ('django.db.models.fields.CharField', [], {'max_length': '20', 'default': "'#999999'"}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'order': ('django.db.models.fields.IntegerField', [], {'default': '10'}),
'project': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'priorities'", 'to': "orm['projects.Project']"})
},
'projects.project': {
'Meta': {'object_name': 'Project', 'ordering': "['name']"},
'created_date': ('django.db.models.fields.DateTimeField', [], {'blank': 'True', 'auto_now_add': 'True'}),
'default_issue_status': ('django.db.models.fields.related.OneToOneField', [], {'unique': 'True', 'related_name': "'+'", 'null': 'True', 'to': "orm['projects.IssueStatus']", 'on_delete': 'models.SET_NULL', 'blank': 'True'}),
'default_issue_type': ('django.db.models.fields.related.OneToOneField', [], {'unique': 'True', 'related_name': "'+'", 'null': 'True', 'to': "orm['projects.IssueType']", 'on_delete': 'models.SET_NULL', 'blank': 'True'}),
'default_points': ('django.db.models.fields.related.OneToOneField', [], {'unique': 'True', 'related_name': "'+'", 'null': 'True', 'to': "orm['projects.Points']", 'on_delete': 'models.SET_NULL', 'blank': 'True'}),
'default_priority': ('django.db.models.fields.related.OneToOneField', [], {'unique': 'True', 'related_name': "'+'", 'null': 'True', 'to': "orm['projects.Priority']", 'on_delete': 'models.SET_NULL', 'blank': 'True'}),
'default_question_status': ('django.db.models.fields.related.OneToOneField', [], {'unique': 'True', 'related_name': "'+'", 'null': 'True', 'to': "orm['projects.QuestionStatus']", 'on_delete': 'models.SET_NULL', 'blank': 'True'}),
'default_severity': ('django.db.models.fields.related.OneToOneField', [], {'unique': 'True', 'related_name': "'+'", 'null': 'True', 'to': "orm['projects.Severity']", 'on_delete': 'models.SET_NULL', 'blank': 'True'}),
'default_task_status': ('django.db.models.fields.related.OneToOneField', [], {'unique': 'True', 'related_name': "'+'", 'null': 'True', 'to': "orm['projects.TaskStatus']", 'on_delete': 'models.SET_NULL', 'blank': 'True'}),
'default_us_status': ('django.db.models.fields.related.OneToOneField', [], {'unique': 'True', 'related_name': "'+'", 'null': 'True', 'to': "orm['projects.UserStoryStatus']", 'on_delete': 'models.SET_NULL', 'blank': 'True'}),
'description': ('django.db.models.fields.TextField', [], {}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'last_issue_ref': ('django.db.models.fields.BigIntegerField', [], {'default': '1', 'null': 'True'}),
'last_task_ref': ('django.db.models.fields.BigIntegerField', [], {'default': '1', 'null': 'True'}),
'last_us_ref': ('django.db.models.fields.BigIntegerField', [], {'default': '1', 'null': 'True'}),
'members': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'projects'", 'through': "orm['projects.Membership']", 'to': "orm['users.User']"}),
'modified_date': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '250', 'unique': 'True'}),
'owner': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'owned_projects'", 'to': "orm['users.User']"}),
'public': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'site': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': "'projects'", 'null': 'True', 'to': "orm['base.Site']"}),
'slug': ('django.db.models.fields.SlugField', [], {'max_length': '250', 'blank': 'True', 'unique': 'True'}),
'tags': ('picklefield.fields.PickledObjectField', [], {'blank': 'True'}),
'total_milestones': ('django.db.models.fields.IntegerField', [], {'default': '0', 'null': 'True', 'blank': 'True'}),
'total_story_points': ('django.db.models.fields.FloatField', [], {'default': 'None', 'null': 'True'})
},
'projects.questionstatus': {
'Meta': {'object_name': 'QuestionStatus', 'ordering': "['project', 'order', 'name']", 'unique_together': "(('project', 'name'),)"},
'color': ('django.db.models.fields.CharField', [], {'max_length': '20', 'default': "'#999999'"}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'is_closed': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'order': ('django.db.models.fields.IntegerField', [], {'default': '10'}),
'project': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'question_status'", 'to': "orm['projects.Project']"})
},
'projects.severity': {
'Meta': {'object_name': 'Severity', 'ordering': "['project', 'order', 'name']", 'unique_together': "(('project', 'name'),)"},
'color': ('django.db.models.fields.CharField', [], {'max_length': '20', 'default': "'#999999'"}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'order': ('django.db.models.fields.IntegerField', [], {'default': '10'}),
'project': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'severities'", 'to': "orm['projects.Project']"})
},
'projects.taskstatus': {
'Meta': {'object_name': 'TaskStatus', 'ordering': "['project', 'order', 'name']", 'unique_together': "(('project', 'name'),)"},
'color': ('django.db.models.fields.CharField', [], {'max_length': '20', 'default': "'#999999'"}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'is_closed': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'order': ('django.db.models.fields.IntegerField', [], {'default': '10'}),
'project': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'task_statuses'", 'to': "orm['projects.Project']"})
},
'projects.userstorystatus': {
'Meta': {'object_name': 'UserStoryStatus', 'ordering': "['project', 'order', 'name']", 'unique_together': "(('project', 'name'),)"},
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'is_closed': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'order': ('django.db.models.fields.IntegerField', [], {'default': '10'}),
'project': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'us_statuses'", 'to': "orm['projects.Project']"})
},
'users.role': {
'Meta': {'object_name': 'Role', 'ordering': "['order', 'slug']"},
'computable': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '200'}),
'order': ('django.db.models.fields.IntegerField', [], {'default': '10'}),
'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'roles'", 'to': "orm['auth.Permission']"}),
'slug': ('django.db.models.fields.SlugField', [], {'max_length': '250', 'blank': 'True', 'unique': 'True'})
},
'users.user': {
'Meta': {'object_name': 'User', 'ordering': "['username']"},
'color': ('django.db.models.fields.CharField', [], {'max_length': '9', 'default': "'#669933'", 'blank': 'True'}),
'colorize_tags': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'default_language': ('django.db.models.fields.CharField', [], {'max_length': '20', 'default': "''", 'blank': 'True'}),
'default_timezone': ('django.db.models.fields.CharField', [], {'max_length': '20', 'default': "''", 'blank': 'True'}),
'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
'groups': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'user_set'", 'blank': 'True', 'to': "orm['auth.Group']"}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
'notify_changes_by_me': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'notify_level': ('django.db.models.fields.CharField', [], {'max_length': '32', 'default': "'all_owned_projects'"}),
'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
'photo': ('django.db.models.fields.files.FileField', [], {'max_length': '500', 'null': 'True', 'blank': 'True'}),
'token': ('django.db.models.fields.CharField', [], {'max_length': '200', 'default': 'None', 'null': 'True', 'blank': 'True'}),
'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'user_set'", 'blank': 'True', 'to': "orm['auth.Permission']"}),
'username': ('django.db.models.fields.CharField', [], {'max_length': '30', 'unique': 'True'})
}
}
complete_apps = ['projects']

View File

@ -0,0 +1,210 @@
# -*- coding: utf-8 -*-
import datetime
from south.db import db
from south.v2 import DataMigration
from django.db import models
class Migration(DataMigration):
depends_on = (
("base", "0003_initial_sites_data"),
)
def forwards(self, orm):
"Write your forwards methods here."
# Note: Don't use "from appname.models import ModelName".
# Use orm.ModelName to refer to models in this application,
# and orm['appname.ModelName'] for models in other applications.
site = orm["base.Site"].objects.get(pk=1)
for project in orm["projects.Project"].objects.all():
project.site = site
project.save()
for member in orm["projects.Membership"].objects.all():
member.email = member.user.email
member.save()
def backwards(self, orm):
"Write your backwards methods here."
models = {
'auth.group': {
'Meta': {'object_name': 'Group'},
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '80', 'unique': 'True'}),
'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'blank': 'True', 'to': "orm['auth.Permission']"})
},
'auth.permission': {
'Meta': {'object_name': 'Permission', 'unique_together': "(('content_type', 'codename'),)", 'ordering': "('content_type__app_label', 'content_type__model', 'codename')"},
'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
},
'base.site': {
'Meta': {'object_name': 'Site', 'ordering': "('domain',)"},
'domain': ('django.db.models.fields.CharField', [], {'max_length': '255', 'unique': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'public_register': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'scheme': ('django.db.models.fields.CharField', [], {'max_length': '60', 'default': 'None', 'null': 'True'})
},
'contenttypes.contenttype': {
'Meta': {'object_name': 'ContentType', 'unique_together': "(('app_label', 'model'),)", 'ordering': "('name',)", 'db_table': "'django_content_type'"},
'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
},
'projects.attachment': {
'Meta': {'object_name': 'Attachment', 'ordering': "['project', 'created_date']"},
'attached_file': ('django.db.models.fields.files.FileField', [], {'max_length': '500', 'blank': 'True', 'null': 'True'}),
'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
'created_date': ('django.db.models.fields.DateTimeField', [], {'blank': 'True', 'auto_now_add': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'modified_date': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
'object_id': ('django.db.models.fields.PositiveIntegerField', [], {}),
'owner': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'change_attachments'", 'to': "orm['users.User']"}),
'project': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'attachments'", 'to': "orm['projects.Project']"})
},
'projects.issuestatus': {
'Meta': {'object_name': 'IssueStatus', 'unique_together': "(('project', 'name'),)", 'ordering': "['project', 'order', 'name']"},
'color': ('django.db.models.fields.CharField', [], {'max_length': '20', 'default': "'#999999'"}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'is_closed': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'order': ('django.db.models.fields.IntegerField', [], {'default': '10'}),
'project': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'issue_statuses'", 'to': "orm['projects.Project']"})
},
'projects.issuetype': {
'Meta': {'object_name': 'IssueType', 'unique_together': "(('project', 'name'),)", 'ordering': "['project', 'order', 'name']"},
'color': ('django.db.models.fields.CharField', [], {'max_length': '20', 'default': "'#999999'"}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'order': ('django.db.models.fields.IntegerField', [], {'default': '10'}),
'project': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'issue_types'", 'to': "orm['projects.Project']"})
},
'projects.membership': {
'Meta': {'object_name': 'Membership', 'ordering': "['project', 'role']"},
'created_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now', 'blank': 'True', 'auto_now_add': 'True'}),
'email': ('django.db.models.fields.EmailField', [], {'max_length': '255', 'default': 'None', 'null': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'project': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'memberships'", 'to': "orm['projects.Project']"}),
'role': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'memberships'", 'to': "orm['users.Role']"}),
'token': ('django.db.models.fields.CharField', [], {'max_length': '60', 'unique': 'True', 'default': 'None', 'null': 'True', 'blank': 'True'}),
'user': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'memberships'", 'default': 'None', 'null': 'True', 'to': "orm['users.User']"})
},
'projects.points': {
'Meta': {'object_name': 'Points', 'unique_together': "(('project', 'name'),)", 'ordering': "['project', 'order', 'name']"},
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'order': ('django.db.models.fields.IntegerField', [], {'default': '10'}),
'project': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'points'", 'to': "orm['projects.Project']"}),
'value': ('django.db.models.fields.FloatField', [], {'blank': 'True', 'default': 'None', 'null': 'True'})
},
'projects.priority': {
'Meta': {'object_name': 'Priority', 'unique_together': "(('project', 'name'),)", 'ordering': "['project', 'order', 'name']"},
'color': ('django.db.models.fields.CharField', [], {'max_length': '20', 'default': "'#999999'"}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'order': ('django.db.models.fields.IntegerField', [], {'default': '10'}),
'project': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'priorities'", 'to': "orm['projects.Project']"})
},
'projects.project': {
'Meta': {'object_name': 'Project', 'ordering': "['name']"},
'created_date': ('django.db.models.fields.DateTimeField', [], {'blank': 'True', 'auto_now_add': 'True'}),
'default_issue_status': ('django.db.models.fields.related.OneToOneField', [], {'on_delete': 'models.SET_NULL', 'unique': 'True', 'null': 'True', 'related_name': "'+'", 'blank': 'True', 'to': "orm['projects.IssueStatus']"}),
'default_issue_type': ('django.db.models.fields.related.OneToOneField', [], {'on_delete': 'models.SET_NULL', 'unique': 'True', 'null': 'True', 'related_name': "'+'", 'blank': 'True', 'to': "orm['projects.IssueType']"}),
'default_points': ('django.db.models.fields.related.OneToOneField', [], {'on_delete': 'models.SET_NULL', 'unique': 'True', 'null': 'True', 'related_name': "'+'", 'blank': 'True', 'to': "orm['projects.Points']"}),
'default_priority': ('django.db.models.fields.related.OneToOneField', [], {'on_delete': 'models.SET_NULL', 'unique': 'True', 'null': 'True', 'related_name': "'+'", 'blank': 'True', 'to': "orm['projects.Priority']"}),
'default_question_status': ('django.db.models.fields.related.OneToOneField', [], {'on_delete': 'models.SET_NULL', 'unique': 'True', 'null': 'True', 'related_name': "'+'", 'blank': 'True', 'to': "orm['projects.QuestionStatus']"}),
'default_severity': ('django.db.models.fields.related.OneToOneField', [], {'on_delete': 'models.SET_NULL', 'unique': 'True', 'null': 'True', 'related_name': "'+'", 'blank': 'True', 'to': "orm['projects.Severity']"}),
'default_task_status': ('django.db.models.fields.related.OneToOneField', [], {'on_delete': 'models.SET_NULL', 'unique': 'True', 'null': 'True', 'related_name': "'+'", 'blank': 'True', 'to': "orm['projects.TaskStatus']"}),
'default_us_status': ('django.db.models.fields.related.OneToOneField', [], {'on_delete': 'models.SET_NULL', 'unique': 'True', 'null': 'True', 'related_name': "'+'", 'blank': 'True', 'to': "orm['projects.UserStoryStatus']"}),
'description': ('django.db.models.fields.TextField', [], {}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'last_issue_ref': ('django.db.models.fields.BigIntegerField', [], {'default': '1', 'null': 'True'}),
'last_task_ref': ('django.db.models.fields.BigIntegerField', [], {'default': '1', 'null': 'True'}),
'last_us_ref': ('django.db.models.fields.BigIntegerField', [], {'default': '1', 'null': 'True'}),
'members': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'projects'", 'symmetrical': 'False', 'to': "orm['users.User']", 'through': "orm['projects.Membership']"}),
'modified_date': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '250', 'unique': 'True'}),
'owner': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'owned_projects'", 'to': "orm['users.User']"}),
'public': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'site': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'projects'", 'default': 'None', 'null': 'True', 'to': "orm['base.Site']"}),
'slug': ('django.db.models.fields.SlugField', [], {'max_length': '250', 'unique': 'True', 'blank': 'True'}),
'tags': ('picklefield.fields.PickledObjectField', [], {'blank': 'True'}),
'total_milestones': ('django.db.models.fields.IntegerField', [], {'blank': 'True', 'default': '0', 'null': 'True'}),
'total_story_points': ('django.db.models.fields.FloatField', [], {'default': 'None', 'null': 'True'})
},
'projects.questionstatus': {
'Meta': {'object_name': 'QuestionStatus', 'unique_together': "(('project', 'name'),)", 'ordering': "['project', 'order', 'name']"},
'color': ('django.db.models.fields.CharField', [], {'max_length': '20', 'default': "'#999999'"}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'is_closed': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'order': ('django.db.models.fields.IntegerField', [], {'default': '10'}),
'project': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'question_status'", 'to': "orm['projects.Project']"})
},
'projects.severity': {
'Meta': {'object_name': 'Severity', 'unique_together': "(('project', 'name'),)", 'ordering': "['project', 'order', 'name']"},
'color': ('django.db.models.fields.CharField', [], {'max_length': '20', 'default': "'#999999'"}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'order': ('django.db.models.fields.IntegerField', [], {'default': '10'}),
'project': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'severities'", 'to': "orm['projects.Project']"})
},
'projects.taskstatus': {
'Meta': {'object_name': 'TaskStatus', 'unique_together': "(('project', 'name'),)", 'ordering': "['project', 'order', 'name']"},
'color': ('django.db.models.fields.CharField', [], {'max_length': '20', 'default': "'#999999'"}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'is_closed': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'order': ('django.db.models.fields.IntegerField', [], {'default': '10'}),
'project': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'task_statuses'", 'to': "orm['projects.Project']"})
},
'projects.userstorystatus': {
'Meta': {'object_name': 'UserStoryStatus', 'unique_together': "(('project', 'name'),)", 'ordering': "['project', 'order', 'name']"},
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'is_closed': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'order': ('django.db.models.fields.IntegerField', [], {'default': '10'}),
'project': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'us_statuses'", 'to': "orm['projects.Project']"})
},
'users.role': {
'Meta': {'object_name': 'Role', 'ordering': "['order', 'slug']"},
'computable': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '200'}),
'order': ('django.db.models.fields.IntegerField', [], {'default': '10'}),
'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'roles'", 'symmetrical': 'False', 'to': "orm['auth.Permission']"}),
'slug': ('django.db.models.fields.SlugField', [], {'max_length': '250', 'unique': 'True', 'blank': 'True'})
},
'users.user': {
'Meta': {'object_name': 'User', 'ordering': "['username']"},
'color': ('django.db.models.fields.CharField', [], {'max_length': '9', 'default': "'#669933'", 'blank': 'True'}),
'colorize_tags': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'default_language': ('django.db.models.fields.CharField', [], {'max_length': '20', 'default': "''", 'blank': 'True'}),
'default_timezone': ('django.db.models.fields.CharField', [], {'max_length': '20', 'default': "''", 'blank': 'True'}),
'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
'groups': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'user_set'", 'blank': 'True', 'symmetrical': 'False', 'to': "orm['auth.Group']"}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
'notify_changes_by_me': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'notify_level': ('django.db.models.fields.CharField', [], {'max_length': '32', 'default': "'all_owned_projects'"}),
'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
'photo': ('django.db.models.fields.files.FileField', [], {'max_length': '500', 'blank': 'True', 'null': 'True'}),
'token': ('django.db.models.fields.CharField', [], {'max_length': '200', 'blank': 'True', 'default': 'None', 'null': 'True'}),
'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'user_set'", 'blank': 'True', 'symmetrical': 'False', 'to': "orm['auth.Permission']"}),
'username': ('django.db.models.fields.CharField', [], {'max_length': '30', 'unique': 'True'})
}
}
complete_apps = ['projects']
symmetrical = True

View File

@ -1,5 +1,8 @@
# -*- coding: utf-8 -*-
import itertools
import collections
from django.db import models
from django.db.models.loading import get_model
from django.conf import settings
@ -8,104 +11,47 @@ from django.contrib.contenttypes.models import ContentType
from django.contrib.contenttypes import generic
from django.contrib.auth import get_user_model
from django.utils.translation import ugettext_lazy as _
from django.utils import timezone
from picklefield.fields import PickledObjectField
import reversion
from greenmine.base.utils.slug import slugify_uniquely
from greenmine.base.utils.dicts import dict_sum
from greenmine.projects.userstories.models import UserStory
from . import choices
import reversion
import itertools
import collections
def get_attachment_file_path(instance, filename):
return "attachment-files/{project}/{model}/{filename}".format(
project=instance.project.slug,
model=instance.content_type.model,
filename=filename
)
class Attachment(models.Model):
owner = models.ForeignKey(settings.AUTH_USER_MODEL, null=False, blank=False,
related_name="change_attachments",
verbose_name=_("owner"))
project = models.ForeignKey("Project", null=False, blank=False,
related_name="attachments", verbose_name=_("project"))
content_type = models.ForeignKey(ContentType, null=False, blank=False,
verbose_name=_("content type"))
object_id = models.PositiveIntegerField(null=False, blank=False,
verbose_name=_("object id"))
content_object = generic.GenericForeignKey("content_type", "object_id")
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=get_attachment_file_path,
verbose_name=_("attached file"))
class Meta:
verbose_name = "attachment"
verbose_name_plural = "attachments"
ordering = ["project", "created_date"]
permissions = (
("view_attachment", "Can view attachment"),
)
def __str__(self):
return "Attachment: {}".format(self.id)
class Membership(models.Model):
user = models.ForeignKey(settings.AUTH_USER_MODEL, null=False, blank=False,
# This model stores all project memberships. Also
# stores invitations to memberships that does not have
# assigned user.
user = models.ForeignKey(settings.AUTH_USER_MODEL, null=True, blank=True, default=None,
related_name="memberships")
project = models.ForeignKey("Project", null=False, blank=False,
related_name="memberships")
role = models.ForeignKey("users.Role", null=False, blank=False,
related_name="memberships")
# Invitation metadata
email = models.EmailField(max_length=255, default=None, null=True)
created_at = models.DateTimeField(auto_now_add=True, default=timezone.now)
token = models.CharField(max_length=60, unique=True, blank=True, null=True,
default=None)
class Meta:
verbose_name = "membership"
verbose_name_plural = "membershipss"
unique_together = ("user", "project")
ordering = ["project", "role", "user"]
ordering = ["project", "role"]
permissions = (
("view_membership", "Can view membership"),
)
class Project(models.Model):
name = models.CharField(max_length=250, unique=True, null=False, blank=False,
verbose_name=_("name"))
slug = models.SlugField(max_length=250, unique=True, null=False, blank=True,
verbose_name=_("slug"))
description = models.TextField(null=False, blank=False,
verbose_name=_("description"))
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"))
owner = models.ForeignKey(settings.AUTH_USER_MODEL, null=False, blank=False,
related_name="owned_projects", verbose_name=_("owner"))
members = models.ManyToManyField(settings.AUTH_USER_MODEL, related_name="projects",
through="Membership", verbose_name=_("members"))
public = models.BooleanField(default=True, null=False, blank=True,
verbose_name=_("public"))
last_us_ref = models.BigIntegerField(null=True, blank=False, default=1,
verbose_name=_("last us ref"))
last_task_ref = models.BigIntegerField(null=True, blank=False, default=1,
verbose_name=_("last task ref"))
last_issue_ref = models.BigIntegerField(null=True, blank=False, default=1,
verbose_name=_("last issue ref"))
total_milestones = models.IntegerField(default=0, null=True, blank=True,
verbose_name=_("total of milestones"))
total_story_points = models.FloatField(default=None, null=True, blank=False,
verbose_name=_("total story points"))
tags = PickledObjectField(null=False, blank=True, verbose_name=_("tags"))
class ProjectDefaults(models.Model):
default_points = models.OneToOneField("projects.Points", on_delete=models.SET_NULL,
related_name="+", null=True, blank=True,
verbose_name=_("default points"))
@ -136,19 +82,53 @@ class Project(models.Model):
related_name="+", null=True, blank=True,
verbose_name=_("default questions "
"status"))
class Meta:
abstract = True
class Project(ProjectDefaults, models.Model):
name = models.CharField(max_length=250, unique=True, null=False, blank=False,
verbose_name=_("name"))
slug = models.SlugField(max_length=250, unique=True, null=False, blank=True,
verbose_name=_("slug"))
description = models.TextField(null=False, blank=False,
verbose_name=_("description"))
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"))
owner = models.ForeignKey(settings.AUTH_USER_MODEL, null=False, blank=False,
related_name="owned_projects", verbose_name=_("owner"))
members = models.ManyToManyField(settings.AUTH_USER_MODEL, related_name="projects",
through="Membership", verbose_name=_("members"))
public = models.BooleanField(default=True, null=False, blank=True,
verbose_name=_("public"))
last_us_ref = models.BigIntegerField(null=True, blank=False, default=1,
verbose_name=_("last us ref"))
last_task_ref = models.BigIntegerField(null=True, blank=False, default=1,
verbose_name=_("last task ref"))
last_issue_ref = models.BigIntegerField(null=True, blank=False, default=1,
verbose_name=_("last issue ref"))
total_milestones = models.IntegerField(default=0, null=True, blank=True,
verbose_name=_("total of milestones"))
total_story_points = models.FloatField(default=None, null=True, blank=False,
verbose_name=_("total story points"))
tags = PickledObjectField(null=False, blank=True, verbose_name=_("tags"))
site = models.ForeignKey("base.Site", related_name="projects", null=True, default=None)
notifiable_fields = [
"name",
"total_milestones",
"total_story_points",
"default_points",
"default_us_status",
"default_task_status",
"default_priority",
"default_severity",
"default_issue_status",
"default_issue_type",
"default_question_status",
# This realy should be in this list?
# "default_points",
# "default_us_status",
# "default_task_status",
# "default_priority",
# "default_severity",
# "default_issue_status",
# "default_issue_type",
# "default_question_status",
"description"
]
@ -178,9 +158,8 @@ class Project(models.Model):
def get_users(self):
user_model = get_user_model()
return user_model.objects.filter(
id__in=list(self.memberships.values_list("user", flat=True))
)
members = self.memberships.values_list("user", flat=True)
return user_model.objects.filter(id__in=list(members))
def update_role_points(self):
rolepoints_model = get_model("userstories", "RolePoints")
@ -251,6 +230,45 @@ class Project(models.Model):
return self._get_user_stories_points(self.user_stories.filter(milestone__isnull=False))
def get_attachment_file_path(instance, filename):
return "attachment-files/{project}/{model}/{filename}".format(
project=instance.project.slug,
model=instance.content_type.model,
filename=filename
)
class Attachment(models.Model):
owner = models.ForeignKey(settings.AUTH_USER_MODEL, null=False, blank=False,
related_name="change_attachments",
verbose_name=_("owner"))
project = models.ForeignKey("Project", null=False, blank=False,
related_name="attachments", verbose_name=_("project"))
content_type = models.ForeignKey(ContentType, null=False, blank=False,
verbose_name=_("content type"))
object_id = models.PositiveIntegerField(null=False, blank=False,
verbose_name=_("object id"))
content_object = generic.GenericForeignKey("content_type", "object_id")
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=get_attachment_file_path,
verbose_name=_("attached file"))
class Meta:
verbose_name = "attachment"
verbose_name_plural = "attachments"
ordering = ["project", "created_date"]
permissions = (
("view_attachment", "Can view attachment"),
)
def __str__(self):
return "Attachment: {}".format(self.id)
# User Stories common Models
class UserStoryStatus(models.Model):
name = models.CharField(max_length=255, null=False, blank=False,

View File

@ -89,6 +89,12 @@ class QuestionStatusSerializer(serializers.ModelSerializer):
# Projects
class MembershipSerializer(serializers.ModelSerializer):
class Meta:
model = models.Membership
read_only_fields = ("user",)
class ProjectMembershipSerializer(serializers.ModelSerializer):
role_name = serializers.CharField(source='role.name', required=False)
full_name = serializers.CharField(source='user.get_full_name', required=False)
@ -101,14 +107,14 @@ class ProjectSerializer(serializers.ModelSerializer):
class Meta:
model = models.Project
read_only_fields = ("created_date", "modified_date", "owner")
read_only_fields = ("created_date", "modified_date", "owner", "site")
exclude = ("last_us_ref", "last_task_ref", "last_issue_ref")
class ProjectDetailSerializer(ProjectSerializer):
list_of_milestones = serializers.SerializerMethodField("get_list_of_milestones")
roles = serializers.SerializerMethodField("get_list_of_roles")
memberships = MembershipSerializer(many=True, required=False)
memberships = ProjectMembershipSerializer(many=True, required=False)
us_statuses = UserStoryStatusSerializer(many=True, required=False) # User Stories
points = PointsSerializer(many=True, required=False)
task_statuses = TaskStatusSerializer(many=True, required=False) # Tasks

View File

@ -0,0 +1,23 @@
{% extends "emails/base.jinja" %}
{% set final_url = resolve_front_url("invitation", membership.token) %}
{% set final_url_name = "Greenmine - Invitation to join on {0} project.".format(membership.project) %}
{% block body %}
<table border="0" width="100%" cellpadding="0" cellspacing="0" class="table-body">
<tr>
<td>
<p>Hi,</p>
<p>you have been invited to the project '{{ membership.project }}'.</p>
<p>If you want to join to this project go to <a href="{{ final_url }}">this link</a>
for accept this invitation.</p>
</td>
</tr>
</table>
{% endblock %}
{% block footer %}
<p style="padding: 10px; border-top: 1px solid #eee;">
More info at: <a href="{{ final_url }}" style="color: #666;">{{ final_url_name }}</a>
</p>
{% endblock %}

View File

@ -0,0 +1,11 @@
{% set final_url = resolve_front_url("invitation", membership.token) %}
{% set final_url_name = "Greenmine - Invitation to join on {0} project.".format(membership.project) %}
Hi,
you have been invited to the project '{{ membership.project }}'.
If you want to join to this project go to {{ final_url }} for accept this invitation.
** More info at ({{ final_url }}) **

View File

@ -0,0 +1 @@
[Greenmine] Invitation to join to the project '{{ membership.project}}'

View File

@ -20,8 +20,7 @@ def create_project(id, owner, save=True):
def add_membership(project, user, role_slug=None):
model = get_model("users", "Role")
roles = model.objects.filter(slug=role_slug)
role = roles[0] if roles.exists() else model.objects.all()[0]
role = model.objects.get(slug=role_slug)
model = get_model("projects", "Membership")
instance = model.objects.create(

View File

@ -8,12 +8,12 @@ from django.core import mail
from django.db.models import get_model
from greenmine.base.users.tests import create_user
from greenmine.projects.models import Project
from greenmine.projects.models import Project, Membership
from . import create_project, add_membership
class ProfileTestCase(test.TestCase):
fixtures = ["initial_role.json", ]
fixtures = ["initial_role.json", "initial_site.json"]
def setUp(self):
self.user1 = create_user(1, is_superuser=True)
@ -24,9 +24,10 @@ class ProfileTestCase(test.TestCase):
self.project2 = create_project(2, self.user1)
self.project3 = create_project(3, self.user2)
add_membership(self.project1, self.user3, "dev")
add_membership(self.project3, self.user3, "dev")
add_membership(self.project3, self.user2, "dev")
add_membership(self.project1, self.user3, "back")
add_membership(self.project3, self.user3, "back")
add_membership(self.project3, self.user2, "back")
def test_list_users(self):
response = self.client.login(username=self.user3.username,
@ -37,7 +38,7 @@ class ProfileTestCase(test.TestCase):
self.assertEqual(response.status_code, 200)
users_list = response.data
self.assertEqual(len(users_list), 2)
self.assertEqual(len(users_list), 3)
def test_update_users(self):
@ -153,19 +154,79 @@ class ProfileTestCase(test.TestCase):
class ProjectsTestCase(test.TestCase):
fixtures = ["initial_role.json", ]
fixtures = ["initial_role.json", "initial_site.json"]
def setUp(self):
self.user1 = create_user(1)
self.user2 = create_user(2)
self.user3 = create_user(3)
self.user3 = create_user(4)
self.project1 = create_project(1, self.user1)
self.project2 = create_project(2, self.user1)
self.project3 = create_project(3, self.user2)
self.project4 = create_project(4, self.user2)
add_membership(self.project1, self.user3, "dev")
add_membership(self.project3, self.user3, "dev")
add_membership(self.project1, self.user3, "back")
add_membership(self.project3, self.user3, "back")
self.dev_role = get_model("users", "Role").objects.get(slug="back")
def test_send_invitations_01(self):
response = self.client.login(username=self.user1.username,
password=self.user1.username)
self.assertTrue(response)
url = reverse("memberships-list")
data = {"role": self.dev_role.id,
"email": "pepe@pepe.com",
"project": self.project4.id}
response = self.client.post(url, data=json.dumps(data), content_type="application/json")
self.assertEqual(response.status_code, 201)
self.assertEqual(self.project4.memberships.count(), 1)
self.assertEqual(len(mail.outbox), 1)
self.assertNotEqual(len(mail.outbox[0].body), 0)
def test_send_invitations_02(self):
response = self.client.login(username=self.user1.username,
password=self.user1.username)
self.assertTrue(response)
url = reverse("memberships-list")
data = {"role": self.dev_role.id,
"email": "pepe@pepe.com",
"project": self.project4.id}
response = self.client.post(url, data=json.dumps(data), content_type="application/json")
self.assertEqual(response.status_code, 201)
self.assertEqual(self.project4.memberships.count(), 1)
self.assertEqual(len(mail.outbox), 1)
self.assertNotEqual(len(mail.outbox[0].body), 0)
response = self.client.post(url, data=json.dumps(data), content_type="application/json")
self.assertEqual(self.project4.memberships.count(), 1)
self.assertEqual(response.status_code, 400)
self.assertEqual(len(mail.outbox), 1)
self.assertNotEqual(len(mail.outbox[0].body), 0)
def test_send_invitations_03(self):
response = self.client.login(username=self.user1.username,
password=self.user1.username)
self.assertTrue(response)
url = reverse("memberships-list")
data = {"role": self.dev_role.id,
"email": self.user3.email,
"project": self.project3.id}
response = self.client.post(url, data=json.dumps(data), content_type="application/json")
self.assertEqual(response.status_code, 400)
self.assertEqual(len(mail.outbox), 0)
def test_list_projects_by_anon(self):
response = self.client.get(reverse("projects-list"))
@ -187,7 +248,7 @@ class ProjectsTestCase(test.TestCase):
response = self.client.get(reverse("projects-list"))
self.assertEqual(response.status_code, 200)
projects_list = response.data
self.assertEqual(len(projects_list), 1)
self.assertEqual(len(projects_list), 2)
self.client.logout()
def test_list_projects_by_membership(self):
@ -239,13 +300,13 @@ class ProjectsTestCase(test.TestCase):
"total_story_points": 10
}
self.assertEqual(Project.objects.all().count(), 3)
self.assertEqual(Project.objects.all().count(), 4)
response = self.client.post(
reverse("projects-list"),
json.dumps(data),
content_type="application/json")
self.assertEqual(response.status_code, 401)
self.assertEqual(Project.objects.all().count(), 3)
self.assertEqual(Project.objects.all().count(), 4)
def test_create_project_by_auth(self):
data = {
@ -254,16 +315,15 @@ class ProjectsTestCase(test.TestCase):
"total_story_points": 10
}
self.assertEqual(Project.objects.all().count(), 3)
self.assertEqual(Project.objects.all().count(), 4)
response = self.client.login(username=self.user1.username,
password=self.user1.username)
self.assertTrue(response)
response = self.client.post(
reverse("projects-list"),
json.dumps(data),
content_type="application/json")
response = self.client.post(reverse("projects-list"), json.dumps(data),
content_type="application/json")
self.assertEqual(response.status_code, 201)
self.assertEqual(Project.objects.all().count(), 4)
self.assertEqual(Project.objects.all().count(), 5)
self.client.logout()
def test_edit_project_by_anon(self):
@ -271,21 +331,21 @@ class ProjectsTestCase(test.TestCase):
"description": "Edited project description",
}
self.assertEqual(Project.objects.all().count(), 3)
self.assertEqual(Project.objects.all().count(), 4)
self.assertNotEqual(data["description"], self.project1.description)
response = self.client.patch(
reverse("projects-detail", args=(self.project1.id,)),
json.dumps(data),
content_type="application/json")
self.assertEqual(response.status_code, 401)
self.assertEqual(Project.objects.all().count(), 3)
self.assertEqual(Project.objects.all().count(), 4)
def test_edit_project_by_owner(self):
data = {
"description": "Modified project description",
}
self.assertEqual(Project.objects.all().count(), 3)
self.assertEqual(Project.objects.all().count(), 4)
self.assertNotEqual(data["description"], self.project1.description)
response = self.client.login(username=self.user1.username,
password=self.user1.username)
@ -296,7 +356,7 @@ class ProjectsTestCase(test.TestCase):
content_type="application/json")
self.assertEqual(response.status_code, 200)
self.assertEqual(data["description"], response.data["description"])
self.assertEqual(Project.objects.all().count(), 3)
self.assertEqual(Project.objects.all().count(), 4)
self.client.logout()
def test_edit_project_by_membership(self):
@ -304,7 +364,7 @@ class ProjectsTestCase(test.TestCase):
"description": "Edited project description",
}
self.assertEqual(Project.objects.all().count(), 3)
self.assertEqual(Project.objects.all().count(), 4)
self.assertNotEqual(data["description"], self.project1.description)
response = self.client.login(username=self.user3.username,
password=self.user3.username)
@ -315,7 +375,7 @@ class ProjectsTestCase(test.TestCase):
content_type="application/json")
self.assertEqual(response.status_code, 200)
self.assertEqual(data["description"], response.data["description"])
self.assertEqual(Project.objects.all().count(), 3)
self.assertEqual(Project.objects.all().count(), 4)
self.client.logout()
def test_edit_project_by_not_membership(self):
@ -323,7 +383,7 @@ class ProjectsTestCase(test.TestCase):
"description": "Edited project description",
}
self.assertEqual(Project.objects.all().count(), 3)
self.assertEqual(Project.objects.all().count(), 4)
self.assertNotEqual(data["description"], self.project1.description)
response = self.client.login(username=self.user2.username,
password=self.user2.username)
@ -333,41 +393,41 @@ class ProjectsTestCase(test.TestCase):
json.dumps(data),
content_type="application/json")
self.assertEqual(response.status_code, 404)
self.assertEqual(Project.objects.all().count(), 3)
self.assertEqual(Project.objects.all().count(), 4)
self.client.logout()
def test_delete_project_by_anon(self):
self.assertEqual(Project.objects.all().count(), 3)
self.assertEqual(Project.objects.all().count(), 4)
response = self.client.delete(reverse("projects-detail", args=(self.project1.id,)))
self.assertEqual(response.status_code, 401)
self.assertEqual(Project.objects.all().count(), 3)
self.assertEqual(Project.objects.all().count(), 4)
def test_delete_project_by_owner(self):
self.assertEqual(Project.objects.all().count(), 3)
self.assertEqual(Project.objects.all().count(), 4)
response = self.client.login(username=self.user1.username,
password=self.user1.username)
self.assertTrue(response)
response = self.client.delete(reverse("projects-detail", args=(self.project1.id,)))
self.assertEqual(response.status_code, 204)
self.assertEqual(Project.objects.all().count(), 2)
self.assertEqual(Project.objects.all().count(), 3)
self.client.logout()
def test_delete_project_by_membership(self):
self.assertEqual(Project.objects.all().count(), 3)
self.assertEqual(Project.objects.all().count(), 4)
response = self.client.login(username=self.user3.username,
password=self.user3.username)
self.assertTrue(response)
response = self.client.delete(reverse("projects-detail", args=(self.project1.id,)))
self.assertEqual(response.status_code, 204)
self.assertEqual(Project.objects.all().count(), 2)
self.assertEqual(Project.objects.all().count(), 3)
self.client.logout()
def test_delete_project_by_not_membership(self):
self.assertEqual(Project.objects.all().count(), 3)
self.assertEqual(Project.objects.all().count(), 4)
response = self.client.login(username=self.user1.username,
password=self.user1.username)
self.assertTrue(response)
response = self.client.delete(reverse("projects-detail", args=(self.project3.id,)))
self.assertEqual(response.status_code, 404)
self.assertEqual(Project.objects.all().count(), 3)
self.assertEqual(Project.objects.all().count(), 4)
self.client.logout()

View File

@ -4,7 +4,7 @@ from greenmine.base import routers
from greenmine.base.auth.api import AuthViewSet
from greenmine.base.users.api import RolesViewSet, UsersViewSet
from greenmine.base.searches.api import SearchViewSet
from greenmine.projects.api import ProjectViewSet, MembershipViewSet
from greenmine.projects.api import ProjectViewSet, MembershipViewSet, InvitationViewSet
from greenmine.projects.milestones.api import MilestoneViewSet
from greenmine.projects.userstories.api import UserStoryViewSet, UserStoryAttachmentViewSet
from greenmine.projects.tasks.api import TaskViewSet, TaskAttachmentViewSet
@ -27,6 +27,7 @@ router.register(r"search", SearchViewSet, base_name="search")
# greenmine.projects
router.register(r"projects", ProjectViewSet, base_name="projects")
router.register(r"memberships", MembershipViewSet, base_name="memberships")
router.register(r"invitations", InvitationViewSet, base_name="invitations")
# greenmine.projects.milestones
router.register(r"milestones", MilestoneViewSet, base_name="milestones")

View File

@ -15,6 +15,8 @@ OUT_PROJECT_ROOT = os.path.abspath(
os.path.join(PROJECT_ROOT, "..")
)
USE_X_FORWARDED_HOST = True
APPEND_SLASH = False
@ -151,17 +153,17 @@ TEMPLATE_LOADERS = [
]
MIDDLEWARE_CLASSES = [
'greenmine.base.middleware.CoorsMiddleware',
'greenmine.base.middleware.SitesMiddleware',
# Common middlewares
'django.middleware.common.CommonMiddleware',
'django.middleware.locale.LocaleMiddleware',
'greenmine.base.middleware.CoorsMiddleware',
# Only needed by django admin
'django.contrib.sessions.middleware.SessionMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
# 'greenmine.base.middleware.GreenmineSessionMiddleware',
]
TEMPLATE_CONTEXT_PROCESSORS = [
@ -182,9 +184,6 @@ TEMPLATE_DIRS = [
]
INSTALLED_APPS = [
# 'grappelli.dashboard',
# 'grappelli',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
@ -192,10 +191,10 @@ INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.staticfiles',
'greenmine.base',
'greenmine.base.notifications',
'greenmine.base.users',
'greenmine.base.notifications',
'greenmine.base.searches',
'greenmine.base',
'greenmine.projects',
'greenmine.projects.milestones',
'greenmine.projects.userstories',
@ -210,7 +209,6 @@ INSTALLED_APPS = [
'reversion',
'rest_framework',
'djmail',
'django_sites',
]
WSGI_APPLICATION = 'greenmine.wsgi.application'
@ -224,9 +222,12 @@ LOGGING = {
}
},
'formatters': {
'simple': {
'complete': {
'format': '%(levelname)s:%(asctime)s:%(module)s %(message)s'
},
'simple': {
'format': '%(levelname)s:%(asctime)s: %(message)s'
},
'null': {
'format': '%(message)s',
},
@ -239,7 +240,7 @@ LOGGING = {
'console':{
'level':'DEBUG',
'class':'logging.StreamHandler',
'formatter': 'null',
'formatter': 'simple',
},
'mail_admins': {
'level': 'ERROR',
@ -258,11 +259,16 @@ LOGGING = {
'level': 'ERROR',
'propagate': False,
},
'main': {
'greenmine': {
'handlers': ['console'],
'level': 'DEBUG',
'propagate': False,
}
},
'greenmine.site': {
'handlers': ['console'],
'level': 'DEBUG',
'propagate': False,
},
}
}
@ -291,6 +297,7 @@ REST_FRAMEWORK = {
'greenmine.base.auth.Session',
),
'FILTER_BACKEND': 'greenmine.base.filters.FilterBackend',
'EXCEPTION_HANDLER': 'greenmine.base.exceptions.exception_handler',
'PAGINATE_BY': 30,
'MAX_PAGINATE_BY': 1000,
}

View File

@ -13,6 +13,7 @@ admin.autodiscover()
urlpatterns = patterns('',
url(r'^api/v1/', include(router.urls)),
url(r'^api/v1/api-auth/', include('rest_framework.urls', namespace='rest_framework')),
url(r'^api/v1/sites', "greenmine.base.apiviews.sitestatus"),
url(r'^admin/', include(admin.site.urls)),
url(r'^grappelli/', include('grappelli.urls')),
)

View File

@ -8,6 +8,8 @@ createdb greenmine
echo "-> Run syncdb"
python manage.py syncdb --migrate --noinput --traceback
# echo "-> Load initial Site"
# python manage.py loaddata initial_site --traceback
echo "-> Load initial user"
python manage.py loaddata initial_user --traceback
echo "-> Load initial roles"

View File

@ -2,7 +2,7 @@ git+https://github.com/tomchristie/django-rest-framework.git@2.4.0
git+https://github.com/etianen/django-reversion.git@django-1.6
Django==1.6.0
South==0.8.2
South==0.8.3
anyjson==0.3.3
Werkzeug==0.9.4
celery==3.0.24
@ -19,4 +19,3 @@ djmail>=0.4
django-jinja>=0.21
jinja2==2.7.1
pygments>=1.6
django-sites>=0.4