Initial sites & invitations implementation.
parent
98cd05c857
commit
6d0e03c98d
|
@ -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)
|
|
@ -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"})
|
|
@ -2,21 +2,135 @@
|
||||||
|
|
||||||
from django.db.models.loading import get_model
|
from django.db.models.loading import get_model
|
||||||
from django.contrib.auth import logout, login, authenticate
|
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.response import Response
|
||||||
from rest_framework.permissions import AllowAny
|
from rest_framework.permissions import AllowAny
|
||||||
from rest_framework import status, viewsets
|
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 exceptions as exc
|
||||||
from greenmine.base import auth
|
from greenmine.base import auth
|
||||||
|
|
||||||
from greenmine.base.users.models import User, Role
|
from .serializers import (PublicRegisterSerializer,
|
||||||
from greenmine.base.users.serializers import UserSerializer
|
PrivateRegisterSerializer,
|
||||||
|
PrivateGenericRegisterSerializer,
|
||||||
|
PrivateRegisterExistingSerializer)
|
||||||
|
|
||||||
|
|
||||||
class AuthViewSet(viewsets.ViewSet):
|
class AuthViewSet(viewsets.ViewSet):
|
||||||
permission_classes = (AllowAny,)
|
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):
|
def create(self, request, **kwargs):
|
||||||
username = request.DATA.get('username', None)
|
username = request.DATA.get('username', None)
|
||||||
password = request.DATA.get('password', None)
|
password = request.DATA.get('password', None)
|
||||||
|
@ -29,7 +143,5 @@ class AuthViewSet(viewsets.ViewSet):
|
||||||
if not user.check_password(password):
|
if not user.check_password(password):
|
||||||
raise exc.BadRequest("Invalid username or password")
|
raise exc.BadRequest("Invalid username or password")
|
||||||
|
|
||||||
serializer = UserSerializer(user)
|
response_data = self._create_response(user)
|
||||||
response_data = serializer.data
|
|
||||||
response_data["auth_token"] = auth.get_token_for_user(user)
|
|
||||||
return Response(response_data, status=status.HTTP_200_OK)
|
return Response(response_data, status=status.HTTP_200_OK)
|
||||||
|
|
|
@ -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)
|
|
@ -1,19 +1,28 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
import uuid
|
||||||
|
import json
|
||||||
|
|
||||||
from django.core.urlresolvers import reverse
|
from django.core.urlresolvers import reverse
|
||||||
from django.conf.urls import patterns, include, url
|
from django.conf.urls import patterns, include, url
|
||||||
from django import test
|
from django import test
|
||||||
|
from django.db.models import get_model
|
||||||
|
|
||||||
from rest_framework.views import APIView
|
from rest_framework.views import APIView
|
||||||
|
from rest_framework import viewsets
|
||||||
from rest_framework.permissions import IsAuthenticated
|
from rest_framework.permissions import IsAuthenticated
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
|
|
||||||
from greenmine import urls
|
from greenmine import urls
|
||||||
from greenmine.base import auth
|
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,)
|
authentication_classes = (auth.Token,)
|
||||||
permission_classes = (IsAuthenticated,)
|
permission_classes = (IsAuthenticated,)
|
||||||
|
|
||||||
|
@ -22,11 +31,12 @@ class TestAuthView(APIView):
|
||||||
|
|
||||||
|
|
||||||
urls.urlpatterns += patterns("",
|
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):
|
def setUp(self):
|
||||||
self.user1 = create_user(1)
|
self.user1 = create_user(1)
|
||||||
|
|
||||||
|
@ -41,3 +51,129 @@ class SimpleTokenAuthTests(test.TestCase):
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
self.assertEqual(response.content, b'"ok"')
|
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
|
||||||
|
|
|
@ -2,6 +2,12 @@
|
||||||
|
|
||||||
from rest_framework import exceptions
|
from rest_framework import exceptions
|
||||||
from rest_framework import status
|
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):
|
class BaseException(exceptions.APIException):
|
||||||
|
@ -66,3 +72,53 @@ class NotAuthenticated(exceptions.NotAuthenticated):
|
||||||
exception.
|
exception.
|
||||||
"""
|
"""
|
||||||
pass
|
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
|
||||||
|
|
|
@ -0,0 +1,10 @@
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"model": "base.site",
|
||||||
|
"fields": {
|
||||||
|
"domain": "localhost",
|
||||||
|
"name": "localhost"
|
||||||
|
},
|
||||||
|
"pk": 1
|
||||||
|
}
|
||||||
|
]
|
|
@ -1,32 +1,22 @@
|
||||||
import time
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
import json
|
||||||
|
|
||||||
from django.conf import settings
|
|
||||||
from django import http
|
from django import http
|
||||||
from django.utils.cache import patch_vary_headers
|
from greenmine.base import sites
|
||||||
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)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
COORS_ALLOWED_ORIGINS = '*'
|
COORS_ALLOWED_ORIGINS = '*'
|
||||||
COORS_ALLOWED_METHODS = ['POST', 'GET', 'OPTIONS', 'PUT', 'DELETE', 'PATCH']
|
COORS_ALLOWED_METHODS = ['POST', 'GET', 'OPTIONS', 'PUT', 'DELETE', 'PATCH', 'HEAD']
|
||||||
COORS_ALLOWED_HEADERS = ['Content-Type', 'X-Requested-With',
|
COORS_ALLOWED_HEADERS = ['content-type', 'x-requested-with',
|
||||||
'Authorization', 'Accept-Encoding',
|
'authorization', 'accept-encoding',
|
||||||
'X-Disable-Pagination']
|
'x-disable-pagination', 'x-host']
|
||||||
COORS_ALLOWED_CREDENTIALS = True
|
COORS_ALLOWED_CREDENTIALS = True
|
||||||
COORS_EXPOSE_HEADERS = ["x-pagination-count", "x-paginated",
|
COORS_EXPOSE_HEADERS = ["x-pagination-count", "x-paginated", "x-paginated-by",
|
||||||
"x-paginated-by", "x-pagination-current"]
|
"x-paginated-by", "x-pagination-current", "x-site-host",
|
||||||
|
"x-site-register"]
|
||||||
|
|
||||||
|
from .exceptions import format_exception
|
||||||
|
|
||||||
|
|
||||||
class CoorsMiddleware(object):
|
class CoorsMiddleware(object):
|
||||||
|
@ -49,3 +39,28 @@ class CoorsMiddleware(object):
|
||||||
def process_response(self, request, response):
|
def process_response(self, request, response):
|
||||||
self._populate_response(response)
|
self._populate_response(response)
|
||||||
return 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
|
||||||
|
|
|
@ -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']
|
|
@ -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']
|
|
@ -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
|
|
@ -1,8 +1,74 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- 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
|
# Patch api view for correctly return 401 responses on
|
||||||
# request is authenticated instead of 403
|
# request is authenticated instead of 403
|
||||||
from . import monkey
|
from . import monkey
|
||||||
monkey.patch_api_view()
|
monkey.patch_api_view()
|
||||||
monkey.patch_serializer()
|
monkey.patch_serializer()
|
||||||
monkey.patch_import_module()
|
monkey.patch_import_module()
|
||||||
|
monkey.patch_south_hacks()
|
||||||
|
|
|
@ -66,3 +66,18 @@ def patch_import_module():
|
||||||
import importlib
|
import importlib
|
||||||
|
|
||||||
django_importlib.import_module = importlib.import_module
|
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
|
||||||
|
|
|
@ -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
|
|
@ -15,55 +15,17 @@ from djmail.template_mail import MagicMailBuilder
|
||||||
|
|
||||||
from greenmine.base import exceptions as exc
|
from greenmine.base import exceptions as exc
|
||||||
from greenmine.base.filters import FilterBackend
|
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 .models import User, Role
|
||||||
from .serializers import UserSerializer, RoleSerializer, RecoverySerializer
|
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):
|
class UsersViewSet(ModelCrudViewSet):
|
||||||
permission_classes = (IsAuthenticated,)
|
permission_classes = (IsAuthenticated,)
|
||||||
serializer_class = UserSerializer
|
serializer_class = UserSerializer
|
||||||
queryset = User.objects.all()
|
queryset = User.objects.all()
|
||||||
filter_backends = (ProjectMembershipFilter,)
|
|
||||||
filter_fields = [("project", "memberships__project__pk")]
|
filter_fields = [("project", "memberships__project__pk")]
|
||||||
|
|
||||||
def pre_conditions_on_save(self, obj):
|
def pre_conditions_on_save(self, obj):
|
||||||
|
@ -129,3 +91,22 @@ class UsersViewSet(ModelCrudViewSet):
|
||||||
request.user.set_password(password)
|
request.user.set_password(password)
|
||||||
request.user.save(update_fields=["password"])
|
request.user.save(update_fields=["password"])
|
||||||
return Response(status=status.HTTP_204_NO_CONTENT)
|
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)
|
||||||
|
|
|
@ -21,3 +21,14 @@ def create_user(id, save=True, is_superuser=False):
|
||||||
if save:
|
if save:
|
||||||
instance.save()
|
instance.save()
|
||||||
return instance
|
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
|
||||||
|
|
|
@ -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)
|
|
@ -1,10 +1,10 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
import django_sites as sites
|
|
||||||
|
|
||||||
from django_jinja import library
|
from django_jinja import library
|
||||||
|
|
||||||
|
from greenmine.base import sites
|
||||||
|
|
||||||
|
|
||||||
URLS = {
|
URLS = {
|
||||||
"home": "/",
|
"home": "/",
|
||||||
|
@ -15,20 +15,16 @@ URLS = {
|
||||||
"issue": "/#/project/{0}/issues/{1}",
|
"issue": "/#/project/{0}/issues/{1}",
|
||||||
"project-admin": "/#/project/{0}/admin",
|
"project-admin": "/#/project/{0}/admin",
|
||||||
"change-password": "/#/change-password/{0}",
|
"change-password": "/#/change-password/{0}",
|
||||||
|
"invitation": "/#/invitation/{0}",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
lib = library.Library()
|
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")
|
@lib.global_function(name="resolve_front_url")
|
||||||
def resolve(type, *args):
|
def resolve(type, *args):
|
||||||
site = get_current_site()
|
site = sites.get_active_site()
|
||||||
url_tmpl = "{scheme}//{domain}{url}"
|
url_tmpl = "{scheme}//{domain}{url}"
|
||||||
|
|
||||||
scheme = site.scheme and "{0}:".format(site.scheme) or ""
|
scheme = site.scheme and "{0}:".format(site.scheme) or ""
|
||||||
|
|
|
@ -1,13 +1,20 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
import uuid
|
||||||
|
|
||||||
from django.db.models import Q
|
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.response import Response
|
||||||
from rest_framework.decorators import detail_route
|
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 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.base.notifications.api import NotificationSenderMixin
|
||||||
from greenmine.projects.aggregates.tags import get_all_tags
|
from greenmine.projects.aggregates.tags import get_all_tags
|
||||||
|
|
||||||
|
@ -53,6 +60,12 @@ class ProjectViewSet(ModelCrudViewSet):
|
||||||
|
|
||||||
def pre_save(self, obj):
|
def pre_save(self, obj):
|
||||||
obj.owner = self.request.user
|
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)
|
super().pre_save(obj)
|
||||||
|
|
||||||
|
|
||||||
|
@ -61,6 +74,54 @@ class MembershipViewSet(ModelCrudViewSet):
|
||||||
serializer_class = serializers.MembershipSerializer
|
serializer_class = serializers.MembershipSerializer
|
||||||
permission_classes = (IsAuthenticated, permissions.MembershipPermission)
|
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
|
# User Stories commin ViewSets
|
||||||
|
|
||||||
|
|
|
@ -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']
|
|
@ -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
|
|
@ -1,5 +1,8 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
import itertools
|
||||||
|
import collections
|
||||||
|
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.db.models.loading import get_model
|
from django.db.models.loading import get_model
|
||||||
from django.conf import settings
|
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.contenttypes import generic
|
||||||
from django.contrib.auth import get_user_model
|
from django.contrib.auth import get_user_model
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
from django.utils import timezone
|
||||||
|
|
||||||
from picklefield.fields import PickledObjectField
|
from picklefield.fields import PickledObjectField
|
||||||
|
import reversion
|
||||||
|
|
||||||
|
|
||||||
from greenmine.base.utils.slug import slugify_uniquely
|
from greenmine.base.utils.slug import slugify_uniquely
|
||||||
from greenmine.base.utils.dicts import dict_sum
|
from greenmine.base.utils.dicts import dict_sum
|
||||||
from greenmine.projects.userstories.models import UserStory
|
from greenmine.projects.userstories.models import UserStory
|
||||||
from . import choices
|
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):
|
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")
|
related_name="memberships")
|
||||||
project = models.ForeignKey("Project", null=False, blank=False,
|
project = models.ForeignKey("Project", null=False, blank=False,
|
||||||
related_name="memberships")
|
related_name="memberships")
|
||||||
role = models.ForeignKey("users.Role", null=False, blank=False,
|
role = models.ForeignKey("users.Role", null=False, blank=False,
|
||||||
related_name="memberships")
|
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:
|
class Meta:
|
||||||
verbose_name = "membership"
|
verbose_name = "membership"
|
||||||
verbose_name_plural = "membershipss"
|
verbose_name_plural = "membershipss"
|
||||||
unique_together = ("user", "project")
|
ordering = ["project", "role"]
|
||||||
ordering = ["project", "role", "user"]
|
|
||||||
permissions = (
|
permissions = (
|
||||||
("view_membership", "Can view membership"),
|
("view_membership", "Can view membership"),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class Project(models.Model):
|
|
||||||
name = models.CharField(max_length=250, unique=True, null=False, blank=False,
|
class ProjectDefaults(models.Model):
|
||||||
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"))
|
|
||||||
default_points = models.OneToOneField("projects.Points", on_delete=models.SET_NULL,
|
default_points = models.OneToOneField("projects.Points", on_delete=models.SET_NULL,
|
||||||
related_name="+", null=True, blank=True,
|
related_name="+", null=True, blank=True,
|
||||||
verbose_name=_("default points"))
|
verbose_name=_("default points"))
|
||||||
|
@ -136,19 +82,53 @@ class Project(models.Model):
|
||||||
related_name="+", null=True, blank=True,
|
related_name="+", null=True, blank=True,
|
||||||
verbose_name=_("default questions "
|
verbose_name=_("default questions "
|
||||||
"status"))
|
"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 = [
|
notifiable_fields = [
|
||||||
"name",
|
"name",
|
||||||
"total_milestones",
|
"total_milestones",
|
||||||
"total_story_points",
|
"total_story_points",
|
||||||
"default_points",
|
# This realy should be in this list?
|
||||||
"default_us_status",
|
# "default_points",
|
||||||
"default_task_status",
|
# "default_us_status",
|
||||||
"default_priority",
|
# "default_task_status",
|
||||||
"default_severity",
|
# "default_priority",
|
||||||
"default_issue_status",
|
# "default_severity",
|
||||||
"default_issue_type",
|
# "default_issue_status",
|
||||||
"default_question_status",
|
# "default_issue_type",
|
||||||
|
# "default_question_status",
|
||||||
"description"
|
"description"
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -178,9 +158,8 @@ class Project(models.Model):
|
||||||
|
|
||||||
def get_users(self):
|
def get_users(self):
|
||||||
user_model = get_user_model()
|
user_model = get_user_model()
|
||||||
return user_model.objects.filter(
|
members = self.memberships.values_list("user", flat=True)
|
||||||
id__in=list(self.memberships.values_list("user", flat=True))
|
return user_model.objects.filter(id__in=list(members))
|
||||||
)
|
|
||||||
|
|
||||||
def update_role_points(self):
|
def update_role_points(self):
|
||||||
rolepoints_model = get_model("userstories", "RolePoints")
|
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))
|
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
|
# User Stories common Models
|
||||||
class UserStoryStatus(models.Model):
|
class UserStoryStatus(models.Model):
|
||||||
name = models.CharField(max_length=255, null=False, blank=False,
|
name = models.CharField(max_length=255, null=False, blank=False,
|
||||||
|
|
|
@ -89,6 +89,12 @@ class QuestionStatusSerializer(serializers.ModelSerializer):
|
||||||
# Projects
|
# Projects
|
||||||
|
|
||||||
class MembershipSerializer(serializers.ModelSerializer):
|
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)
|
role_name = serializers.CharField(source='role.name', required=False)
|
||||||
full_name = serializers.CharField(source='user.get_full_name', required=False)
|
full_name = serializers.CharField(source='user.get_full_name', required=False)
|
||||||
|
|
||||||
|
@ -101,14 +107,14 @@ class ProjectSerializer(serializers.ModelSerializer):
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = models.Project
|
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")
|
exclude = ("last_us_ref", "last_task_ref", "last_issue_ref")
|
||||||
|
|
||||||
|
|
||||||
class ProjectDetailSerializer(ProjectSerializer):
|
class ProjectDetailSerializer(ProjectSerializer):
|
||||||
list_of_milestones = serializers.SerializerMethodField("get_list_of_milestones")
|
list_of_milestones = serializers.SerializerMethodField("get_list_of_milestones")
|
||||||
roles = serializers.SerializerMethodField("get_list_of_roles")
|
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
|
us_statuses = UserStoryStatusSerializer(many=True, required=False) # User Stories
|
||||||
points = PointsSerializer(many=True, required=False)
|
points = PointsSerializer(many=True, required=False)
|
||||||
task_statuses = TaskStatusSerializer(many=True, required=False) # Tasks
|
task_statuses = TaskStatusSerializer(many=True, required=False) # Tasks
|
||||||
|
|
|
@ -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 %}
|
|
@ -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 }}) **
|
|
@ -0,0 +1 @@
|
||||||
|
[Greenmine] Invitation to join to the project '{{ membership.project}}'
|
|
@ -20,8 +20,7 @@ def create_project(id, owner, save=True):
|
||||||
|
|
||||||
def add_membership(project, user, role_slug=None):
|
def add_membership(project, user, role_slug=None):
|
||||||
model = get_model("users", "Role")
|
model = get_model("users", "Role")
|
||||||
roles = model.objects.filter(slug=role_slug)
|
role = model.objects.get(slug=role_slug)
|
||||||
role = roles[0] if roles.exists() else model.objects.all()[0]
|
|
||||||
|
|
||||||
model = get_model("projects", "Membership")
|
model = get_model("projects", "Membership")
|
||||||
instance = model.objects.create(
|
instance = model.objects.create(
|
||||||
|
|
|
@ -8,12 +8,12 @@ from django.core import mail
|
||||||
from django.db.models import get_model
|
from django.db.models import get_model
|
||||||
|
|
||||||
from greenmine.base.users.tests import create_user
|
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
|
from . import create_project, add_membership
|
||||||
|
|
||||||
class ProfileTestCase(test.TestCase):
|
class ProfileTestCase(test.TestCase):
|
||||||
fixtures = ["initial_role.json", ]
|
fixtures = ["initial_role.json", "initial_site.json"]
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.user1 = create_user(1, is_superuser=True)
|
self.user1 = create_user(1, is_superuser=True)
|
||||||
|
@ -24,9 +24,10 @@ class ProfileTestCase(test.TestCase):
|
||||||
self.project2 = create_project(2, self.user1)
|
self.project2 = create_project(2, self.user1)
|
||||||
self.project3 = create_project(3, self.user2)
|
self.project3 = create_project(3, self.user2)
|
||||||
|
|
||||||
add_membership(self.project1, self.user3, "dev")
|
add_membership(self.project1, self.user3, "back")
|
||||||
add_membership(self.project3, self.user3, "dev")
|
add_membership(self.project3, self.user3, "back")
|
||||||
add_membership(self.project3, self.user2, "dev")
|
add_membership(self.project3, self.user2, "back")
|
||||||
|
|
||||||
|
|
||||||
def test_list_users(self):
|
def test_list_users(self):
|
||||||
response = self.client.login(username=self.user3.username,
|
response = self.client.login(username=self.user3.username,
|
||||||
|
@ -37,7 +38,7 @@ class ProfileTestCase(test.TestCase):
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
|
|
||||||
users_list = response.data
|
users_list = response.data
|
||||||
self.assertEqual(len(users_list), 2)
|
self.assertEqual(len(users_list), 3)
|
||||||
|
|
||||||
|
|
||||||
def test_update_users(self):
|
def test_update_users(self):
|
||||||
|
@ -153,19 +154,79 @@ class ProfileTestCase(test.TestCase):
|
||||||
|
|
||||||
|
|
||||||
class ProjectsTestCase(test.TestCase):
|
class ProjectsTestCase(test.TestCase):
|
||||||
fixtures = ["initial_role.json", ]
|
fixtures = ["initial_role.json", "initial_site.json"]
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.user1 = create_user(1)
|
self.user1 = create_user(1)
|
||||||
self.user2 = create_user(2)
|
self.user2 = create_user(2)
|
||||||
self.user3 = create_user(3)
|
self.user3 = create_user(3)
|
||||||
|
self.user3 = create_user(4)
|
||||||
|
|
||||||
self.project1 = create_project(1, self.user1)
|
self.project1 = create_project(1, self.user1)
|
||||||
self.project2 = create_project(2, self.user1)
|
self.project2 = create_project(2, self.user1)
|
||||||
self.project3 = create_project(3, self.user2)
|
self.project3 = create_project(3, self.user2)
|
||||||
|
self.project4 = create_project(4, self.user2)
|
||||||
|
|
||||||
add_membership(self.project1, self.user3, "dev")
|
add_membership(self.project1, self.user3, "back")
|
||||||
add_membership(self.project3, self.user3, "dev")
|
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):
|
def test_list_projects_by_anon(self):
|
||||||
response = self.client.get(reverse("projects-list"))
|
response = self.client.get(reverse("projects-list"))
|
||||||
|
@ -187,7 +248,7 @@ class ProjectsTestCase(test.TestCase):
|
||||||
response = self.client.get(reverse("projects-list"))
|
response = self.client.get(reverse("projects-list"))
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
projects_list = response.data
|
projects_list = response.data
|
||||||
self.assertEqual(len(projects_list), 1)
|
self.assertEqual(len(projects_list), 2)
|
||||||
self.client.logout()
|
self.client.logout()
|
||||||
|
|
||||||
def test_list_projects_by_membership(self):
|
def test_list_projects_by_membership(self):
|
||||||
|
@ -239,13 +300,13 @@ class ProjectsTestCase(test.TestCase):
|
||||||
"total_story_points": 10
|
"total_story_points": 10
|
||||||
}
|
}
|
||||||
|
|
||||||
self.assertEqual(Project.objects.all().count(), 3)
|
self.assertEqual(Project.objects.all().count(), 4)
|
||||||
response = self.client.post(
|
response = self.client.post(
|
||||||
reverse("projects-list"),
|
reverse("projects-list"),
|
||||||
json.dumps(data),
|
json.dumps(data),
|
||||||
content_type="application/json")
|
content_type="application/json")
|
||||||
self.assertEqual(response.status_code, 401)
|
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):
|
def test_create_project_by_auth(self):
|
||||||
data = {
|
data = {
|
||||||
|
@ -254,16 +315,15 @@ class ProjectsTestCase(test.TestCase):
|
||||||
"total_story_points": 10
|
"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,
|
response = self.client.login(username=self.user1.username,
|
||||||
password=self.user1.username)
|
password=self.user1.username)
|
||||||
self.assertTrue(response)
|
self.assertTrue(response)
|
||||||
response = self.client.post(
|
response = self.client.post(reverse("projects-list"), json.dumps(data),
|
||||||
reverse("projects-list"),
|
content_type="application/json")
|
||||||
json.dumps(data),
|
|
||||||
content_type="application/json")
|
|
||||||
self.assertEqual(response.status_code, 201)
|
self.assertEqual(response.status_code, 201)
|
||||||
self.assertEqual(Project.objects.all().count(), 4)
|
self.assertEqual(Project.objects.all().count(), 5)
|
||||||
self.client.logout()
|
self.client.logout()
|
||||||
|
|
||||||
def test_edit_project_by_anon(self):
|
def test_edit_project_by_anon(self):
|
||||||
|
@ -271,21 +331,21 @@ class ProjectsTestCase(test.TestCase):
|
||||||
"description": "Edited project description",
|
"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)
|
self.assertNotEqual(data["description"], self.project1.description)
|
||||||
response = self.client.patch(
|
response = self.client.patch(
|
||||||
reverse("projects-detail", args=(self.project1.id,)),
|
reverse("projects-detail", args=(self.project1.id,)),
|
||||||
json.dumps(data),
|
json.dumps(data),
|
||||||
content_type="application/json")
|
content_type="application/json")
|
||||||
self.assertEqual(response.status_code, 401)
|
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):
|
def test_edit_project_by_owner(self):
|
||||||
data = {
|
data = {
|
||||||
"description": "Modified project description",
|
"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)
|
self.assertNotEqual(data["description"], self.project1.description)
|
||||||
response = self.client.login(username=self.user1.username,
|
response = self.client.login(username=self.user1.username,
|
||||||
password=self.user1.username)
|
password=self.user1.username)
|
||||||
|
@ -296,7 +356,7 @@ class ProjectsTestCase(test.TestCase):
|
||||||
content_type="application/json")
|
content_type="application/json")
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
self.assertEqual(data["description"], response.data["description"])
|
self.assertEqual(data["description"], response.data["description"])
|
||||||
self.assertEqual(Project.objects.all().count(), 3)
|
self.assertEqual(Project.objects.all().count(), 4)
|
||||||
self.client.logout()
|
self.client.logout()
|
||||||
|
|
||||||
def test_edit_project_by_membership(self):
|
def test_edit_project_by_membership(self):
|
||||||
|
@ -304,7 +364,7 @@ class ProjectsTestCase(test.TestCase):
|
||||||
"description": "Edited project description",
|
"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)
|
self.assertNotEqual(data["description"], self.project1.description)
|
||||||
response = self.client.login(username=self.user3.username,
|
response = self.client.login(username=self.user3.username,
|
||||||
password=self.user3.username)
|
password=self.user3.username)
|
||||||
|
@ -315,7 +375,7 @@ class ProjectsTestCase(test.TestCase):
|
||||||
content_type="application/json")
|
content_type="application/json")
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
self.assertEqual(data["description"], response.data["description"])
|
self.assertEqual(data["description"], response.data["description"])
|
||||||
self.assertEqual(Project.objects.all().count(), 3)
|
self.assertEqual(Project.objects.all().count(), 4)
|
||||||
self.client.logout()
|
self.client.logout()
|
||||||
|
|
||||||
def test_edit_project_by_not_membership(self):
|
def test_edit_project_by_not_membership(self):
|
||||||
|
@ -323,7 +383,7 @@ class ProjectsTestCase(test.TestCase):
|
||||||
"description": "Edited project description",
|
"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)
|
self.assertNotEqual(data["description"], self.project1.description)
|
||||||
response = self.client.login(username=self.user2.username,
|
response = self.client.login(username=self.user2.username,
|
||||||
password=self.user2.username)
|
password=self.user2.username)
|
||||||
|
@ -333,41 +393,41 @@ class ProjectsTestCase(test.TestCase):
|
||||||
json.dumps(data),
|
json.dumps(data),
|
||||||
content_type="application/json")
|
content_type="application/json")
|
||||||
self.assertEqual(response.status_code, 404)
|
self.assertEqual(response.status_code, 404)
|
||||||
self.assertEqual(Project.objects.all().count(), 3)
|
self.assertEqual(Project.objects.all().count(), 4)
|
||||||
self.client.logout()
|
self.client.logout()
|
||||||
|
|
||||||
def test_delete_project_by_anon(self):
|
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,)))
|
response = self.client.delete(reverse("projects-detail", args=(self.project1.id,)))
|
||||||
self.assertEqual(response.status_code, 401)
|
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):
|
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,
|
response = self.client.login(username=self.user1.username,
|
||||||
password=self.user1.username)
|
password=self.user1.username)
|
||||||
self.assertTrue(response)
|
self.assertTrue(response)
|
||||||
response = self.client.delete(reverse("projects-detail", args=(self.project1.id,)))
|
response = self.client.delete(reverse("projects-detail", args=(self.project1.id,)))
|
||||||
self.assertEqual(response.status_code, 204)
|
self.assertEqual(response.status_code, 204)
|
||||||
self.assertEqual(Project.objects.all().count(), 2)
|
self.assertEqual(Project.objects.all().count(), 3)
|
||||||
self.client.logout()
|
self.client.logout()
|
||||||
|
|
||||||
def test_delete_project_by_membership(self):
|
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,
|
response = self.client.login(username=self.user3.username,
|
||||||
password=self.user3.username)
|
password=self.user3.username)
|
||||||
self.assertTrue(response)
|
self.assertTrue(response)
|
||||||
response = self.client.delete(reverse("projects-detail", args=(self.project1.id,)))
|
response = self.client.delete(reverse("projects-detail", args=(self.project1.id,)))
|
||||||
self.assertEqual(response.status_code, 204)
|
self.assertEqual(response.status_code, 204)
|
||||||
self.assertEqual(Project.objects.all().count(), 2)
|
self.assertEqual(Project.objects.all().count(), 3)
|
||||||
self.client.logout()
|
self.client.logout()
|
||||||
|
|
||||||
def test_delete_project_by_not_membership(self):
|
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,
|
response = self.client.login(username=self.user1.username,
|
||||||
password=self.user1.username)
|
password=self.user1.username)
|
||||||
self.assertTrue(response)
|
self.assertTrue(response)
|
||||||
response = self.client.delete(reverse("projects-detail", args=(self.project3.id,)))
|
response = self.client.delete(reverse("projects-detail", args=(self.project3.id,)))
|
||||||
self.assertEqual(response.status_code, 404)
|
self.assertEqual(response.status_code, 404)
|
||||||
self.assertEqual(Project.objects.all().count(), 3)
|
self.assertEqual(Project.objects.all().count(), 4)
|
||||||
self.client.logout()
|
self.client.logout()
|
||||||
|
|
|
@ -4,7 +4,7 @@ from greenmine.base import routers
|
||||||
from greenmine.base.auth.api import AuthViewSet
|
from greenmine.base.auth.api import AuthViewSet
|
||||||
from greenmine.base.users.api import RolesViewSet, UsersViewSet
|
from greenmine.base.users.api import RolesViewSet, UsersViewSet
|
||||||
from greenmine.base.searches.api import SearchViewSet
|
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.milestones.api import MilestoneViewSet
|
||||||
from greenmine.projects.userstories.api import UserStoryViewSet, UserStoryAttachmentViewSet
|
from greenmine.projects.userstories.api import UserStoryViewSet, UserStoryAttachmentViewSet
|
||||||
from greenmine.projects.tasks.api import TaskViewSet, TaskAttachmentViewSet
|
from greenmine.projects.tasks.api import TaskViewSet, TaskAttachmentViewSet
|
||||||
|
@ -27,6 +27,7 @@ router.register(r"search", SearchViewSet, base_name="search")
|
||||||
# greenmine.projects
|
# greenmine.projects
|
||||||
router.register(r"projects", ProjectViewSet, base_name="projects")
|
router.register(r"projects", ProjectViewSet, base_name="projects")
|
||||||
router.register(r"memberships", MembershipViewSet, base_name="memberships")
|
router.register(r"memberships", MembershipViewSet, base_name="memberships")
|
||||||
|
router.register(r"invitations", InvitationViewSet, base_name="invitations")
|
||||||
|
|
||||||
# greenmine.projects.milestones
|
# greenmine.projects.milestones
|
||||||
router.register(r"milestones", MilestoneViewSet, base_name="milestones")
|
router.register(r"milestones", MilestoneViewSet, base_name="milestones")
|
||||||
|
|
|
@ -15,6 +15,8 @@ OUT_PROJECT_ROOT = os.path.abspath(
|
||||||
os.path.join(PROJECT_ROOT, "..")
|
os.path.join(PROJECT_ROOT, "..")
|
||||||
)
|
)
|
||||||
|
|
||||||
|
USE_X_FORWARDED_HOST = True
|
||||||
|
|
||||||
APPEND_SLASH = False
|
APPEND_SLASH = False
|
||||||
|
|
||||||
|
|
||||||
|
@ -151,17 +153,17 @@ TEMPLATE_LOADERS = [
|
||||||
]
|
]
|
||||||
|
|
||||||
MIDDLEWARE_CLASSES = [
|
MIDDLEWARE_CLASSES = [
|
||||||
|
'greenmine.base.middleware.CoorsMiddleware',
|
||||||
|
'greenmine.base.middleware.SitesMiddleware',
|
||||||
|
|
||||||
# Common middlewares
|
# Common middlewares
|
||||||
'django.middleware.common.CommonMiddleware',
|
'django.middleware.common.CommonMiddleware',
|
||||||
'django.middleware.locale.LocaleMiddleware',
|
'django.middleware.locale.LocaleMiddleware',
|
||||||
'greenmine.base.middleware.CoorsMiddleware',
|
|
||||||
|
|
||||||
# Only needed by django admin
|
# Only needed by django admin
|
||||||
'django.contrib.sessions.middleware.SessionMiddleware',
|
'django.contrib.sessions.middleware.SessionMiddleware',
|
||||||
'django.contrib.auth.middleware.AuthenticationMiddleware',
|
'django.contrib.auth.middleware.AuthenticationMiddleware',
|
||||||
'django.contrib.messages.middleware.MessageMiddleware',
|
'django.contrib.messages.middleware.MessageMiddleware',
|
||||||
|
|
||||||
# 'greenmine.base.middleware.GreenmineSessionMiddleware',
|
|
||||||
]
|
]
|
||||||
|
|
||||||
TEMPLATE_CONTEXT_PROCESSORS = [
|
TEMPLATE_CONTEXT_PROCESSORS = [
|
||||||
|
@ -182,9 +184,6 @@ TEMPLATE_DIRS = [
|
||||||
]
|
]
|
||||||
|
|
||||||
INSTALLED_APPS = [
|
INSTALLED_APPS = [
|
||||||
# 'grappelli.dashboard',
|
|
||||||
# 'grappelli',
|
|
||||||
|
|
||||||
'django.contrib.auth',
|
'django.contrib.auth',
|
||||||
'django.contrib.contenttypes',
|
'django.contrib.contenttypes',
|
||||||
'django.contrib.sessions',
|
'django.contrib.sessions',
|
||||||
|
@ -192,10 +191,10 @@ INSTALLED_APPS = [
|
||||||
'django.contrib.admin',
|
'django.contrib.admin',
|
||||||
'django.contrib.staticfiles',
|
'django.contrib.staticfiles',
|
||||||
|
|
||||||
'greenmine.base',
|
|
||||||
'greenmine.base.notifications',
|
|
||||||
'greenmine.base.users',
|
'greenmine.base.users',
|
||||||
|
'greenmine.base.notifications',
|
||||||
'greenmine.base.searches',
|
'greenmine.base.searches',
|
||||||
|
'greenmine.base',
|
||||||
'greenmine.projects',
|
'greenmine.projects',
|
||||||
'greenmine.projects.milestones',
|
'greenmine.projects.milestones',
|
||||||
'greenmine.projects.userstories',
|
'greenmine.projects.userstories',
|
||||||
|
@ -210,7 +209,6 @@ INSTALLED_APPS = [
|
||||||
'reversion',
|
'reversion',
|
||||||
'rest_framework',
|
'rest_framework',
|
||||||
'djmail',
|
'djmail',
|
||||||
'django_sites',
|
|
||||||
]
|
]
|
||||||
|
|
||||||
WSGI_APPLICATION = 'greenmine.wsgi.application'
|
WSGI_APPLICATION = 'greenmine.wsgi.application'
|
||||||
|
@ -224,9 +222,12 @@ LOGGING = {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
'formatters': {
|
'formatters': {
|
||||||
'simple': {
|
'complete': {
|
||||||
'format': '%(levelname)s:%(asctime)s:%(module)s %(message)s'
|
'format': '%(levelname)s:%(asctime)s:%(module)s %(message)s'
|
||||||
},
|
},
|
||||||
|
'simple': {
|
||||||
|
'format': '%(levelname)s:%(asctime)s: %(message)s'
|
||||||
|
},
|
||||||
'null': {
|
'null': {
|
||||||
'format': '%(message)s',
|
'format': '%(message)s',
|
||||||
},
|
},
|
||||||
|
@ -239,7 +240,7 @@ LOGGING = {
|
||||||
'console':{
|
'console':{
|
||||||
'level':'DEBUG',
|
'level':'DEBUG',
|
||||||
'class':'logging.StreamHandler',
|
'class':'logging.StreamHandler',
|
||||||
'formatter': 'null',
|
'formatter': 'simple',
|
||||||
},
|
},
|
||||||
'mail_admins': {
|
'mail_admins': {
|
||||||
'level': 'ERROR',
|
'level': 'ERROR',
|
||||||
|
@ -258,11 +259,16 @@ LOGGING = {
|
||||||
'level': 'ERROR',
|
'level': 'ERROR',
|
||||||
'propagate': False,
|
'propagate': False,
|
||||||
},
|
},
|
||||||
'main': {
|
'greenmine': {
|
||||||
'handlers': ['console'],
|
'handlers': ['console'],
|
||||||
'level': 'DEBUG',
|
'level': 'DEBUG',
|
||||||
'propagate': False,
|
'propagate': False,
|
||||||
}
|
},
|
||||||
|
'greenmine.site': {
|
||||||
|
'handlers': ['console'],
|
||||||
|
'level': 'DEBUG',
|
||||||
|
'propagate': False,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -291,6 +297,7 @@ REST_FRAMEWORK = {
|
||||||
'greenmine.base.auth.Session',
|
'greenmine.base.auth.Session',
|
||||||
),
|
),
|
||||||
'FILTER_BACKEND': 'greenmine.base.filters.FilterBackend',
|
'FILTER_BACKEND': 'greenmine.base.filters.FilterBackend',
|
||||||
|
'EXCEPTION_HANDLER': 'greenmine.base.exceptions.exception_handler',
|
||||||
'PAGINATE_BY': 30,
|
'PAGINATE_BY': 30,
|
||||||
'MAX_PAGINATE_BY': 1000,
|
'MAX_PAGINATE_BY': 1000,
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,6 +13,7 @@ admin.autodiscover()
|
||||||
urlpatterns = patterns('',
|
urlpatterns = patterns('',
|
||||||
url(r'^api/v1/', include(router.urls)),
|
url(r'^api/v1/', include(router.urls)),
|
||||||
url(r'^api/v1/api-auth/', include('rest_framework.urls', namespace='rest_framework')),
|
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'^admin/', include(admin.site.urls)),
|
||||||
url(r'^grappelli/', include('grappelli.urls')),
|
url(r'^grappelli/', include('grappelli.urls')),
|
||||||
)
|
)
|
||||||
|
|
|
@ -8,6 +8,8 @@ createdb greenmine
|
||||||
|
|
||||||
echo "-> Run syncdb"
|
echo "-> Run syncdb"
|
||||||
python manage.py syncdb --migrate --noinput --traceback
|
python manage.py syncdb --migrate --noinput --traceback
|
||||||
|
# echo "-> Load initial Site"
|
||||||
|
# python manage.py loaddata initial_site --traceback
|
||||||
echo "-> Load initial user"
|
echo "-> Load initial user"
|
||||||
python manage.py loaddata initial_user --traceback
|
python manage.py loaddata initial_user --traceback
|
||||||
echo "-> Load initial roles"
|
echo "-> Load initial roles"
|
||||||
|
|
|
@ -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
|
git+https://github.com/etianen/django-reversion.git@django-1.6
|
||||||
|
|
||||||
Django==1.6.0
|
Django==1.6.0
|
||||||
South==0.8.2
|
South==0.8.3
|
||||||
anyjson==0.3.3
|
anyjson==0.3.3
|
||||||
Werkzeug==0.9.4
|
Werkzeug==0.9.4
|
||||||
celery==3.0.24
|
celery==3.0.24
|
||||||
|
@ -19,4 +19,3 @@ djmail>=0.4
|
||||||
django-jinja>=0.21
|
django-jinja>=0.21
|
||||||
jinja2==2.7.1
|
jinja2==2.7.1
|
||||||
pygments>=1.6
|
pygments>=1.6
|
||||||
django-sites>=0.4
|
|
||||||
|
|
Loading…
Reference in New Issue