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.contrib.auth import logout, login, authenticate
|
||||
from django.shortcuts import get_object_or_404
|
||||
|
||||
from rest_framework.response import Response
|
||||
from rest_framework.permissions import AllowAny
|
||||
from rest_framework import status, viewsets
|
||||
from rest_framework.decorators import list_route
|
||||
|
||||
from greenmine.base.models import SiteMember
|
||||
from greenmine.base.sites import get_active_site
|
||||
from greenmine.base.users.models import User, Role
|
||||
from greenmine.base.users.serializers import UserSerializer
|
||||
from greenmine.base import exceptions as exc
|
||||
from greenmine.base import auth
|
||||
|
||||
from greenmine.base.users.models import User, Role
|
||||
from greenmine.base.users.serializers import UserSerializer
|
||||
from .serializers import (PublicRegisterSerializer,
|
||||
PrivateRegisterSerializer,
|
||||
PrivateGenericRegisterSerializer,
|
||||
PrivateRegisterExistingSerializer)
|
||||
|
||||
|
||||
class AuthViewSet(viewsets.ViewSet):
|
||||
permission_classes = (AllowAny,)
|
||||
|
||||
def _create_response(self, user):
|
||||
serializer = UserSerializer(user)
|
||||
response_data = serializer.data
|
||||
response_data["auth_token"] = auth.get_token_for_user(user)
|
||||
return response_data
|
||||
|
||||
def _create_site_member(self, user):
|
||||
site = get_active_site()
|
||||
|
||||
if SiteMember.objects.filter(site=site, user=user).count() == 0:
|
||||
site_member = SiteMember(site=site, user=user, email=user.email,
|
||||
is_owner=False, is_staff=False)
|
||||
site_member.save()
|
||||
|
||||
def _send_public_register_email(self, user):
|
||||
context = {"user": user}
|
||||
|
||||
mbuilder = MagicMailBuilder()
|
||||
email = mbuilder.public_register_user(user.email, context)
|
||||
email.send()
|
||||
|
||||
def _public_register(self, request):
|
||||
if not request.site.public_register:
|
||||
raise exc.BadRequest("Public register is disabled for this site.")
|
||||
|
||||
serializer = PublicRegisterSerializer(data=request.DATA)
|
||||
if not serializer.is_valid():
|
||||
raise exc.BadRequest(serializer.errors)
|
||||
|
||||
data = serializer.data
|
||||
|
||||
user = User(username=data["username"],
|
||||
first_name=data["first_name"],
|
||||
last_name=data["last_name"],
|
||||
email=data["email"])
|
||||
user.set_password(data["password"])
|
||||
user.save()
|
||||
|
||||
self._create_site_member(user)
|
||||
|
||||
#self._send_public_register_email(user)
|
||||
|
||||
response_data = self._create_response(user)
|
||||
return Response(response_data, status=status.HTTP_201_CREATED)
|
||||
|
||||
def _send_private_register_email(self, user, **kwargs):
|
||||
context = {"user": user}
|
||||
context.update(kwargs)
|
||||
|
||||
mbuilder = MagicMailBuilder()
|
||||
email = mbuilder.private_register_user(user.email, context)
|
||||
email.send()
|
||||
|
||||
def _private_register(self, request):
|
||||
base_serializer = PrivateGenericRegisterSerializer(data=request.DATA)
|
||||
if not base_serializer.is_valid():
|
||||
raise exc.BadRequest(base_serializer.errors)
|
||||
|
||||
membership_model = get_model("projects", "Membership")
|
||||
try:
|
||||
membership = membership_model.objects.get(token=base_serializer.data["token"])
|
||||
except membership_model.DoesNotExist as e:
|
||||
raise exc.BadRequest("Invalid token") from e
|
||||
|
||||
if base_serializer.data["existing"]:
|
||||
serializer = PrivateRegisterExistingSerializer(data=request.DATA)
|
||||
if not serializer.is_valid():
|
||||
raise exc.BadRequest(serializer.errors)
|
||||
|
||||
user = get_object_or_404(User, username=serializer.data["username"])
|
||||
if not user.check_password(serializer.data["password"]):
|
||||
raise exc.BadRequest({"password": "Incorrect password"})
|
||||
|
||||
else:
|
||||
serializer = PrivateRegisterSerializer(data=request.DATA)
|
||||
if not serializer.is_valid():
|
||||
raise exc.BadRequest(serializer.errors)
|
||||
|
||||
data = serializer.data
|
||||
user = User(username=data["username"],
|
||||
first_name=data["first_name"],
|
||||
last_name=data["last_name"],
|
||||
email=data["email"])
|
||||
user.set_password(data["password"])
|
||||
user.save()
|
||||
|
||||
self._create_site_member(user)
|
||||
|
||||
membership.user = user
|
||||
membership.save()
|
||||
|
||||
#self._send_private_register_email(user, membership=membership)
|
||||
|
||||
response_data = self._create_response(user)
|
||||
return Response(response_data, status=status.HTTP_201_CREATED)
|
||||
|
||||
@list_route(methods=["POST"], permission_classes=[AllowAny])
|
||||
def register(self, request, **kwargs):
|
||||
type = request.DATA.get("type", None)
|
||||
if type == "public":
|
||||
return self._public_register(request)
|
||||
elif type == "private":
|
||||
return self._private_register(request)
|
||||
|
||||
raise exc.BadRequest("invalid register type")
|
||||
|
||||
def create(self, request, **kwargs):
|
||||
username = request.DATA.get('username', None)
|
||||
password = request.DATA.get('password', None)
|
||||
|
@ -29,7 +143,5 @@ class AuthViewSet(viewsets.ViewSet):
|
|||
if not user.check_password(password):
|
||||
raise exc.BadRequest("Invalid username or password")
|
||||
|
||||
serializer = UserSerializer(user)
|
||||
response_data = serializer.data
|
||||
response_data["auth_token"] = auth.get_token_for_user(user)
|
||||
response_data = self._create_response(user)
|
||||
return Response(response_data, status=status.HTTP_200_OK)
|
||||
|
|
|
@ -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 -*-
|
||||
|
||||
import uuid
|
||||
import json
|
||||
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.conf.urls import patterns, include, url
|
||||
from django import test
|
||||
from django.db.models import get_model
|
||||
|
||||
from rest_framework.views import APIView
|
||||
from rest_framework import viewsets
|
||||
from rest_framework.permissions import IsAuthenticated
|
||||
from rest_framework.response import Response
|
||||
|
||||
from greenmine import urls
|
||||
from greenmine.base import auth
|
||||
from greenmine.base.users.tests import create_user
|
||||
from greenmine.base.users.tests import create_user, create_site
|
||||
from greenmine.projects.tests import create_project
|
||||
|
||||
from greenmine.base.models import Site, SiteMember
|
||||
from greenmine.projects.models import Membership
|
||||
|
||||
|
||||
class TestAuthView(APIView):
|
||||
class TestAuthView(viewsets.ViewSet):
|
||||
authentication_classes = (auth.Token,)
|
||||
permission_classes = (IsAuthenticated,)
|
||||
|
||||
|
@ -22,11 +31,12 @@ class TestAuthView(APIView):
|
|||
|
||||
|
||||
urls.urlpatterns += patterns("",
|
||||
url(r'^test-api/v1/auth/', TestAuthView.as_view(), name="test-token-auth"),
|
||||
url(r'^test-api/v1/auth/', TestAuthView.as_view({"get": "get"}), name="test-token-auth"),
|
||||
)
|
||||
|
||||
|
||||
class SimpleTokenAuthTests(test.TestCase):
|
||||
class TokenAuthTests(test.TestCase):
|
||||
fixtures = ["initial_site.json"]
|
||||
def setUp(self):
|
||||
self.user1 = create_user(1)
|
||||
|
||||
|
@ -41,3 +51,129 @@ class SimpleTokenAuthTests(test.TestCase):
|
|||
self.assertEqual(response.status_code, 200)
|
||||
self.assertEqual(response.content, b'"ok"')
|
||||
|
||||
|
||||
class RegisterTests(test.TestCase):
|
||||
def setUp(self):
|
||||
self.user1 = create_user(1)
|
||||
self.site1 = create_site("localhost1", True)
|
||||
self.site2 = create_site("localhost2", False)
|
||||
self.role = self._create_role()
|
||||
self.project = create_project(1, self.user1)
|
||||
|
||||
def test_public_register_01(self):
|
||||
data = {
|
||||
"username": "pepe",
|
||||
"password": "pepepepe",
|
||||
"first_name": "pepe",
|
||||
"last_name": "pepe",
|
||||
"email": "pepe@pepe.com",
|
||||
"type": "public",
|
||||
}
|
||||
|
||||
url = reverse("auth-register")
|
||||
response = self.client.post(url, data, HTTP_X_HOST=self.site1.name)
|
||||
self.assertEqual(response.status_code, 201)
|
||||
|
||||
self.assertEqual(SiteMember.objects.filter(site=self.site1).count(), 1)
|
||||
self.assertEqual(self.project.memberships.count(), 0)
|
||||
|
||||
|
||||
def test_public_register_02(self):
|
||||
data = {
|
||||
"username": "pepe",
|
||||
"password": "pepepepe",
|
||||
"first_name": "pepe",
|
||||
"last_name": "pepe",
|
||||
"email": "pepe@pepe.com",
|
||||
"type": "public",
|
||||
}
|
||||
|
||||
url = reverse("auth-register")
|
||||
response = self.client.post(url, data, HTTP_X_HOST=self.site2.name)
|
||||
self.assertEqual(response.status_code, 400)
|
||||
|
||||
def test_private_register_01(self):
|
||||
data = {
|
||||
"username": "pepe",
|
||||
"password": "pepepepe",
|
||||
"first_name": "pepe",
|
||||
"last_name": "pepe",
|
||||
"email": "pepe@pepe.com",
|
||||
"type": "private",
|
||||
}
|
||||
|
||||
url = reverse("auth-register")
|
||||
response = self.client.post(url, data, HTTP_X_HOST=self.site2.name)
|
||||
self.assertEqual(response.status_code, 400)
|
||||
|
||||
def test_private_register_02(self):
|
||||
membership = self._create_invitation("pepe@pepe.com")
|
||||
|
||||
data = {
|
||||
"username": "pepe",
|
||||
"password": "pepepepe",
|
||||
"first_name": "pepe",
|
||||
"last_name": "pepe",
|
||||
"email": "pepe@pepe.com",
|
||||
"type": "private",
|
||||
"existing": False,
|
||||
"token": membership.token,
|
||||
}
|
||||
|
||||
self.assertEqual(self.project.memberships.exclude(user__isnull=True).count(), 0)
|
||||
|
||||
url = reverse("auth-register")
|
||||
response = self.client.post(url, data=json.dumps(data),
|
||||
content_type="application/json",
|
||||
HTTP_X_HOST=self.site2.name)
|
||||
|
||||
self.assertEqual(response.status_code, 201)
|
||||
self.assertEqual(self.project.memberships.exclude(user__isnull=True).count(), 1)
|
||||
self.assertEqual(self.project.memberships.get().role, self.role)
|
||||
self.assertEqual(SiteMember.objects.filter(site=self.site1).count(), 0)
|
||||
self.assertEqual(SiteMember.objects.filter(site=self.site2).count(), 1)
|
||||
|
||||
def test_private_register_03(self):
|
||||
membership = self._create_invitation("pepe@pepe.com")
|
||||
|
||||
data = {
|
||||
"username": self.user1.username,
|
||||
"password": self.user1.username,
|
||||
"type": "private",
|
||||
"existing": True,
|
||||
"token": membership.token,
|
||||
}
|
||||
|
||||
self.assertEqual(self.project.memberships.exclude(user__isnull=True).count(), 0)
|
||||
|
||||
url = reverse("auth-register")
|
||||
response = self.client.post(url, data=json.dumps(data),
|
||||
content_type="application/json",
|
||||
HTTP_X_HOST=self.site2.name)
|
||||
|
||||
self.assertEqual(response.status_code, 201)
|
||||
self.assertEqual(self.project.memberships.exclude(user__isnull=True).count(), 1)
|
||||
self.assertEqual(self.project.memberships.get().role, self.role)
|
||||
self.assertEqual(SiteMember.objects.filter(site=self.site1).count(), 0)
|
||||
self.assertEqual(SiteMember.objects.filter(site=self.site2).count(), 1)
|
||||
|
||||
|
||||
def _create_invitation(self, email):
|
||||
token = str(uuid.uuid1())
|
||||
membership_model = get_model("projects", "Membership")
|
||||
|
||||
instance = membership_model(project=self.project,
|
||||
email=email,
|
||||
role=self.role,
|
||||
user=None,
|
||||
token=token)
|
||||
instance.save()
|
||||
return instance
|
||||
|
||||
def _create_role(self):
|
||||
role_model = get_model("users", "Role")
|
||||
instance = role_model(name="foo", slug="foo",
|
||||
order=1, computable=True)
|
||||
|
||||
instance.save()
|
||||
return instance
|
||||
|
|
|
@ -2,6 +2,12 @@
|
|||
|
||||
from rest_framework import exceptions
|
||||
from rest_framework import status
|
||||
from rest_framework.response import Response
|
||||
|
||||
from django.core.exceptions import PermissionDenied as DjangoPermissionDenied
|
||||
from django.http import Http404
|
||||
|
||||
from .utils.json import to_json
|
||||
|
||||
|
||||
class BaseException(exceptions.APIException):
|
||||
|
@ -66,3 +72,53 @@ class NotAuthenticated(exceptions.NotAuthenticated):
|
|||
exception.
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
def format_exception(exc):
|
||||
# TODO: this method need a refactor.
|
||||
# TODO: should return in uniform way all exceptions.
|
||||
|
||||
if isinstance(exc.detail, (dict, list, tuple,)):
|
||||
detail = exc.detail
|
||||
else:
|
||||
class_name = exc.__class__.__name__
|
||||
class_module = exc.__class__.__module__
|
||||
detail = {
|
||||
"_error_message": exc.detail,
|
||||
"_error_type": "{0}.{1}".format(class_module, class_name)
|
||||
}
|
||||
|
||||
return detail
|
||||
|
||||
|
||||
def exception_handler(exc):
|
||||
"""
|
||||
Returns the response that should be used for any given exception.
|
||||
|
||||
By default we handle the REST framework `APIException`, and also
|
||||
Django's builtin `Http404` and `PermissionDenied` exceptions.
|
||||
|
||||
Any unhandled exceptions may return `None`, which will cause a 500 error
|
||||
to be raised.
|
||||
"""
|
||||
|
||||
if isinstance(exc, exceptions.APIException):
|
||||
headers = {}
|
||||
if getattr(exc, 'auth_header', None):
|
||||
headers['WWW-Authenticate'] = exc.auth_header
|
||||
if getattr(exc, 'wait', None):
|
||||
headers['X-Throttle-Wait-Seconds'] = '%d' % exc.wait
|
||||
|
||||
detail = format_exception(exc)
|
||||
return Response(detail, status=exc.status_code, headers=headers)
|
||||
|
||||
elif isinstance(exc, Http404):
|
||||
return Response({'_error_message': 'Not found'},
|
||||
status=status.HTTP_404_NOT_FOUND)
|
||||
|
||||
elif isinstance(exc, DjangoPermissionDenied):
|
||||
return Response({'_error_message': 'Permission denied'},
|
||||
status=status.HTTP_403_FORBIDDEN)
|
||||
|
||||
# Note: Unhandled exceptions will raise a 500 error.
|
||||
return None
|
||||
|
|
|
@ -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.utils.cache import patch_vary_headers
|
||||
from django.utils.http import cookie_date
|
||||
from django.utils.importlib import import_module
|
||||
|
||||
from django.contrib.sessions.middleware import SessionMiddleware
|
||||
|
||||
|
||||
class GreenmineSessionMiddleware(SessionMiddleware):
|
||||
def process_request(self, request):
|
||||
engine = import_module(settings.SESSION_ENGINE)
|
||||
session_key = request.META.get(settings.SESSION_HEADER_NAME, None)
|
||||
if not session_key:
|
||||
session_key = request.COOKIES.get(settings.SESSION_COOKIE_NAME, None)
|
||||
request.session = engine.SessionStore(session_key)
|
||||
|
||||
from greenmine.base import sites
|
||||
|
||||
|
||||
COORS_ALLOWED_ORIGINS = '*'
|
||||
COORS_ALLOWED_METHODS = ['POST', 'GET', 'OPTIONS', 'PUT', 'DELETE', 'PATCH']
|
||||
COORS_ALLOWED_HEADERS = ['Content-Type', 'X-Requested-With',
|
||||
'Authorization', 'Accept-Encoding',
|
||||
'X-Disable-Pagination']
|
||||
COORS_ALLOWED_METHODS = ['POST', 'GET', 'OPTIONS', 'PUT', 'DELETE', 'PATCH', 'HEAD']
|
||||
COORS_ALLOWED_HEADERS = ['content-type', 'x-requested-with',
|
||||
'authorization', 'accept-encoding',
|
||||
'x-disable-pagination', 'x-host']
|
||||
COORS_ALLOWED_CREDENTIALS = True
|
||||
COORS_EXPOSE_HEADERS = ["x-pagination-count", "x-paginated",
|
||||
"x-paginated-by", "x-pagination-current"]
|
||||
COORS_EXPOSE_HEADERS = ["x-pagination-count", "x-paginated", "x-paginated-by",
|
||||
"x-paginated-by", "x-pagination-current", "x-site-host",
|
||||
"x-site-register"]
|
||||
|
||||
from .exceptions import format_exception
|
||||
|
||||
|
||||
class CoorsMiddleware(object):
|
||||
|
@ -49,3 +39,28 @@ class CoorsMiddleware(object):
|
|||
def process_response(self, request, response):
|
||||
self._populate_response(response)
|
||||
return response
|
||||
|
||||
|
||||
class SitesMiddleware(object):
|
||||
def process_request(self, request):
|
||||
domain = request.META.get("HTTP_X_HOST", None)
|
||||
if domain is not None:
|
||||
try:
|
||||
site = sites.get_site_for_domain(domain)
|
||||
except sites.SiteNotFound as e:
|
||||
detail = format_exception(e)
|
||||
return http.HttpResponseBadRequest(json.dumps(detail))
|
||||
else:
|
||||
site = sites.get_default_site()
|
||||
|
||||
request.site = site
|
||||
sites.activate(site)
|
||||
|
||||
def process_response(self, request, response):
|
||||
sites.deactivate()
|
||||
|
||||
if hasattr(request, "site"):
|
||||
response["X-Site-Host"] = request.site.domain
|
||||
response["X-Site-Register"] = "on" if request.site.public_register else "off"
|
||||
|
||||
return response
|
||||
|
|
|
@ -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 -*-
|
||||
|
||||
import string
|
||||
|
||||
from django.db import models
|
||||
from django.db.models.signals import pre_save, pre_delete
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.core.exceptions import ValidationError
|
||||
|
||||
from . import sites
|
||||
|
||||
|
||||
def _simple_domain_name_validator(value):
|
||||
"""
|
||||
Validates that the given value contains no whitespaces to prevent common
|
||||
typos.
|
||||
"""
|
||||
if not value:
|
||||
return
|
||||
checks = ((s in value) for s in string.whitespace)
|
||||
if any(checks):
|
||||
raise ValidationError(
|
||||
_("The domain name cannot contain any spaces or tabs."),
|
||||
code='invalid',
|
||||
)
|
||||
|
||||
|
||||
class Site(models.Model):
|
||||
domain = models.CharField(_('domain name'), max_length=255, unique=True,
|
||||
validators=[_simple_domain_name_validator])
|
||||
name = models.CharField(_('display name'), max_length=255)
|
||||
scheme = models.CharField(_('scheme'), max_length=60, null=True, default=None)
|
||||
|
||||
# Site Metadata
|
||||
public_register = models.BooleanField(default=False)
|
||||
|
||||
class Meta:
|
||||
verbose_name = _('site')
|
||||
verbose_name_plural = _('sites')
|
||||
ordering = ('domain',)
|
||||
|
||||
def __str__(self):
|
||||
return self.domain
|
||||
|
||||
|
||||
class SiteMember(models.Model):
|
||||
site = models.ForeignKey("Site", related_name="+")
|
||||
user = models.ForeignKey("users.User", related_name="+", null=True)
|
||||
|
||||
email = models.EmailField(max_length=255)
|
||||
is_owner = models.BooleanField(default=False)
|
||||
is_staff = models.BooleanField(default=False)
|
||||
|
||||
class Meta:
|
||||
ordering = ["email"]
|
||||
verbose_name = "Site Member"
|
||||
verbose_name_plural = "Site Members"
|
||||
unique_together = ("site", "user")
|
||||
|
||||
def __str__(self):
|
||||
return "SiteMember: {0}:{1}".format(self.site, self.user)
|
||||
|
||||
|
||||
pre_save.connect(sites.clear_site_cache, sender=Site)
|
||||
pre_delete.connect(sites.clear_site_cache, sender=Site)
|
||||
|
||||
|
||||
# Patch api view for correctly return 401 responses on
|
||||
# request is authenticated instead of 403
|
||||
from . import monkey
|
||||
monkey.patch_api_view()
|
||||
monkey.patch_serializer()
|
||||
monkey.patch_import_module()
|
||||
monkey.patch_south_hacks()
|
||||
|
|
|
@ -66,3 +66,18 @@ def patch_import_module():
|
|||
import importlib
|
||||
|
||||
django_importlib.import_module = importlib.import_module
|
||||
|
||||
|
||||
def patch_south_hacks():
|
||||
from south.hacks import django_1_0
|
||||
|
||||
orig_set_installed_apps = django_1_0.Hacks.set_installed_apps
|
||||
def set_installed_apps(self, apps, preserve_models=True):
|
||||
return orig_set_installed_apps(self, apps, preserve_models=preserve_models)
|
||||
|
||||
orig__redo_app_cache = django_1_0.Hacks._redo_app_cache
|
||||
def _redo_app_cache(self, preserve_models=True):
|
||||
return orig__redo_app_cache(self, preserve_models=preserve_models)
|
||||
|
||||
django_1_0.Hacks.set_installed_apps = set_installed_apps
|
||||
django_1_0.Hacks._redo_app_cache = _redo_app_cache
|
||||
|
|
|
@ -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.filters import FilterBackend
|
||||
from greenmine.base.api import ModelCrudViewSet
|
||||
from greenmine.base.api import ModelCrudViewSet, RetrieveModelMixin
|
||||
|
||||
from .models import User, Role
|
||||
from .serializers import UserSerializer, RoleSerializer, RecoverySerializer
|
||||
|
||||
|
||||
class RolesViewSet(viewsets.ViewSet):
|
||||
permission_classes = (IsAuthenticated,)
|
||||
serializer_class = RoleSerializer
|
||||
|
||||
def list(self, request, pk=None):
|
||||
queryset = Role.objects.all()
|
||||
serializer = self.serializer_class(queryset, many=True)
|
||||
return Response(serializer.data)
|
||||
|
||||
def retrieve(self, request, pk=None):
|
||||
try:
|
||||
role = Role.objects.get(pk=pk)
|
||||
except Role.DoesNotExist:
|
||||
raise exc.NotFound()
|
||||
|
||||
serializer = self.serializer_class(role)
|
||||
return Response(serializer.data)
|
||||
|
||||
|
||||
class ProjectMembershipFilter(FilterBackend):
|
||||
def filter_queryset(self, request, queryset, view):
|
||||
queryset = super().filter_queryset(request, queryset, view)
|
||||
|
||||
if request.user.is_superuser:
|
||||
return queryset
|
||||
|
||||
project_model = get_model("projects", "Project")
|
||||
own_projects = project_model.objects.filter(members=request.user)
|
||||
|
||||
project = request.QUERY_PARAMS.get('project', None)
|
||||
if project is not None:
|
||||
own_projects = own_projects.filter(pk=project)
|
||||
|
||||
queryset = (queryset.filter(projects__in=own_projects)
|
||||
.order_by('username').distinct())
|
||||
return queryset
|
||||
|
||||
|
||||
class UsersViewSet(ModelCrudViewSet):
|
||||
permission_classes = (IsAuthenticated,)
|
||||
serializer_class = UserSerializer
|
||||
queryset = User.objects.all()
|
||||
filter_backends = (ProjectMembershipFilter,)
|
||||
filter_fields = [("project", "memberships__project__pk")]
|
||||
|
||||
def pre_conditions_on_save(self, obj):
|
||||
|
@ -129,3 +91,22 @@ class UsersViewSet(ModelCrudViewSet):
|
|||
request.user.set_password(password)
|
||||
request.user.save(update_fields=["password"])
|
||||
return Response(status=status.HTTP_204_NO_CONTENT)
|
||||
|
||||
|
||||
class RolesViewSet(viewsets.ViewSet):
|
||||
permission_classes = (IsAuthenticated,)
|
||||
serializer_class = RoleSerializer
|
||||
|
||||
def list(self, request, pk=None):
|
||||
queryset = Role.objects.all()
|
||||
serializer = self.serializer_class(queryset, many=True)
|
||||
return Response(serializer.data)
|
||||
|
||||
def retrieve(self, request, pk=None):
|
||||
try:
|
||||
role = Role.objects.get(pk=pk)
|
||||
except Role.DoesNotExist:
|
||||
raise exc.NotFound()
|
||||
|
||||
serializer = self.serializer_class(role)
|
||||
return Response(serializer.data)
|
||||
|
|
|
@ -21,3 +21,14 @@ def create_user(id, save=True, is_superuser=False):
|
|||
if save:
|
||||
instance.save()
|
||||
return instance
|
||||
|
||||
|
||||
def create_site(name, public_register=False):
|
||||
site_model = get_model("base", "Site")
|
||||
|
||||
instance = site_model(name=name,
|
||||
domain=name,
|
||||
public_register=public_register)
|
||||
|
||||
instance.save()
|
||||
return instance
|
||||
|
|
|
@ -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 -*-
|
||||
|
||||
from django.conf import settings
|
||||
import django_sites as sites
|
||||
|
||||
from django_jinja import library
|
||||
|
||||
from greenmine.base import sites
|
||||
|
||||
|
||||
URLS = {
|
||||
"home": "/",
|
||||
|
@ -15,20 +15,16 @@ URLS = {
|
|||
"issue": "/#/project/{0}/issues/{1}",
|
||||
"project-admin": "/#/project/{0}/admin",
|
||||
"change-password": "/#/change-password/{0}",
|
||||
"invitation": "/#/invitation/{0}",
|
||||
}
|
||||
|
||||
|
||||
lib = library.Library()
|
||||
|
||||
def get_current_site():
|
||||
current_site_id = getattr(settings, "SITE_ID")
|
||||
front_sites = getattr(settings, "SITES_FRONT")
|
||||
return sites.Site(front_sites[current_site_id])
|
||||
|
||||
|
||||
@lib.global_function(name="resolve_front_url")
|
||||
def resolve(type, *args):
|
||||
site = get_current_site()
|
||||
site = sites.get_active_site()
|
||||
url_tmpl = "{scheme}//{domain}{url}"
|
||||
|
||||
scheme = site.scheme and "{0}:".format(site.scheme) or ""
|
||||
|
|
|
@ -1,13 +1,20 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
import uuid
|
||||
|
||||
from django.db.models import Q
|
||||
|
||||
from rest_framework.permissions import IsAuthenticated
|
||||
from rest_framework.permissions import IsAuthenticated, AllowAny
|
||||
from rest_framework.response import Response
|
||||
from rest_framework.decorators import detail_route
|
||||
from rest_framework import viewsets
|
||||
from rest_framework import status
|
||||
|
||||
from djmail.template_mail import MagicMailBuilder
|
||||
|
||||
from greenmine.base import filters
|
||||
from greenmine.base.api import ModelCrudViewSet, ModelListViewSet
|
||||
from greenmine.base import exceptions as exc
|
||||
from greenmine.base.api import ModelCrudViewSet, ModelListViewSet, RetrieveModelMixin
|
||||
from greenmine.base.notifications.api import NotificationSenderMixin
|
||||
from greenmine.projects.aggregates.tags import get_all_tags
|
||||
|
||||
|
@ -53,6 +60,12 @@ class ProjectViewSet(ModelCrudViewSet):
|
|||
|
||||
def pre_save(self, obj):
|
||||
obj.owner = self.request.user
|
||||
|
||||
# Assign site only if it current
|
||||
# value is None
|
||||
if not obj.site:
|
||||
obj.site = self.request.site
|
||||
|
||||
super().pre_save(obj)
|
||||
|
||||
|
||||
|
@ -61,6 +74,54 @@ class MembershipViewSet(ModelCrudViewSet):
|
|||
serializer_class = serializers.MembershipSerializer
|
||||
permission_classes = (IsAuthenticated, permissions.MembershipPermission)
|
||||
|
||||
def create(self, request, *args, **kwargs):
|
||||
serializer = self.get_serializer(data=request.DATA, files=request.FILES)
|
||||
|
||||
if serializer.is_valid():
|
||||
qs = self.model.objects.filter(Q(project_id=serializer.data["project"],
|
||||
user__email=serializer.data["email"]) |
|
||||
Q(project_id=serializer.data["project"],
|
||||
email=serializer.data["email"]))
|
||||
if qs.count() > 0:
|
||||
raise exc.WrongArguments("Already exist user with specified email address.")
|
||||
|
||||
self.pre_save(serializer.object)
|
||||
self.object = serializer.save(force_insert=True)
|
||||
self.post_save(self.object, created=True)
|
||||
headers = self.get_success_headers(serializer.data)
|
||||
return Response(serializer.data, status=status.HTTP_201_CREATED,
|
||||
headers=headers)
|
||||
|
||||
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
def pre_save(self, object):
|
||||
# Only assign new token if a current token value is empty.
|
||||
if not object.token:
|
||||
object.token = str(uuid.uuid1())
|
||||
|
||||
super().pre_save(object)
|
||||
|
||||
def post_save(self, object, created=False):
|
||||
super().post_save(object, created=created)
|
||||
|
||||
if not created:
|
||||
return
|
||||
|
||||
# Send email only if a new membership is created
|
||||
mbuilder = MagicMailBuilder()
|
||||
email = mbuilder.membership_invitation(object.email, {"membership": object})
|
||||
email.send()
|
||||
|
||||
|
||||
class InvitationViewSet(RetrieveModelMixin, viewsets.GenericViewSet):
|
||||
"""
|
||||
Only used by front for get invitation by it token.
|
||||
"""
|
||||
queryset = models.Membership.objects.all()
|
||||
serializer_class = serializers.MembershipSerializer
|
||||
lookup_field = "token"
|
||||
permission_classes = (AllowAny,)
|
||||
|
||||
|
||||
# User Stories commin ViewSets
|
||||
|
||||
|
|
|
@ -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 -*-
|
||||
|
||||
import itertools
|
||||
import collections
|
||||
|
||||
from django.db import models
|
||||
from django.db.models.loading import get_model
|
||||
from django.conf import settings
|
||||
|
@ -8,104 +11,47 @@ from django.contrib.contenttypes.models import ContentType
|
|||
from django.contrib.contenttypes import generic
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.utils import timezone
|
||||
|
||||
from picklefield.fields import PickledObjectField
|
||||
import reversion
|
||||
|
||||
|
||||
from greenmine.base.utils.slug import slugify_uniquely
|
||||
from greenmine.base.utils.dicts import dict_sum
|
||||
from greenmine.projects.userstories.models import UserStory
|
||||
from . import choices
|
||||
|
||||
import reversion
|
||||
import itertools
|
||||
import collections
|
||||
|
||||
|
||||
def get_attachment_file_path(instance, filename):
|
||||
return "attachment-files/{project}/{model}/{filename}".format(
|
||||
project=instance.project.slug,
|
||||
model=instance.content_type.model,
|
||||
filename=filename
|
||||
)
|
||||
|
||||
|
||||
class Attachment(models.Model):
|
||||
owner = models.ForeignKey(settings.AUTH_USER_MODEL, null=False, blank=False,
|
||||
related_name="change_attachments",
|
||||
verbose_name=_("owner"))
|
||||
project = models.ForeignKey("Project", null=False, blank=False,
|
||||
related_name="attachments", verbose_name=_("project"))
|
||||
content_type = models.ForeignKey(ContentType, null=False, blank=False,
|
||||
verbose_name=_("content type"))
|
||||
object_id = models.PositiveIntegerField(null=False, blank=False,
|
||||
verbose_name=_("object id"))
|
||||
content_object = generic.GenericForeignKey("content_type", "object_id")
|
||||
created_date = models.DateTimeField(auto_now_add=True, null=False, blank=False,
|
||||
verbose_name=_("created date"))
|
||||
modified_date = models.DateTimeField(auto_now=True, null=False, blank=False,
|
||||
verbose_name=_("modified date"))
|
||||
attached_file = models.FileField(max_length=500, null=True, blank=True,
|
||||
upload_to=get_attachment_file_path,
|
||||
verbose_name=_("attached file"))
|
||||
|
||||
class Meta:
|
||||
verbose_name = "attachment"
|
||||
verbose_name_plural = "attachments"
|
||||
ordering = ["project", "created_date"]
|
||||
permissions = (
|
||||
("view_attachment", "Can view attachment"),
|
||||
)
|
||||
|
||||
def __str__(self):
|
||||
return "Attachment: {}".format(self.id)
|
||||
|
||||
|
||||
class Membership(models.Model):
|
||||
user = models.ForeignKey(settings.AUTH_USER_MODEL, null=False, blank=False,
|
||||
# This model stores all project memberships. Also
|
||||
# stores invitations to memberships that does not have
|
||||
# assigned user.
|
||||
|
||||
user = models.ForeignKey(settings.AUTH_USER_MODEL, null=True, blank=True, default=None,
|
||||
related_name="memberships")
|
||||
project = models.ForeignKey("Project", null=False, blank=False,
|
||||
related_name="memberships")
|
||||
role = models.ForeignKey("users.Role", null=False, blank=False,
|
||||
related_name="memberships")
|
||||
|
||||
# Invitation metadata
|
||||
email = models.EmailField(max_length=255, default=None, null=True)
|
||||
created_at = models.DateTimeField(auto_now_add=True, default=timezone.now)
|
||||
token = models.CharField(max_length=60, unique=True, blank=True, null=True,
|
||||
default=None)
|
||||
|
||||
class Meta:
|
||||
verbose_name = "membership"
|
||||
verbose_name_plural = "membershipss"
|
||||
unique_together = ("user", "project")
|
||||
ordering = ["project", "role", "user"]
|
||||
ordering = ["project", "role"]
|
||||
permissions = (
|
||||
("view_membership", "Can view membership"),
|
||||
)
|
||||
|
||||
|
||||
class Project(models.Model):
|
||||
name = models.CharField(max_length=250, unique=True, null=False, blank=False,
|
||||
verbose_name=_("name"))
|
||||
slug = models.SlugField(max_length=250, unique=True, null=False, blank=True,
|
||||
verbose_name=_("slug"))
|
||||
description = models.TextField(null=False, blank=False,
|
||||
verbose_name=_("description"))
|
||||
created_date = models.DateTimeField(auto_now_add=True, null=False, blank=False,
|
||||
verbose_name=_("created date"))
|
||||
modified_date = models.DateTimeField(auto_now=True, null=False, blank=False,
|
||||
verbose_name=_("modified date"))
|
||||
owner = models.ForeignKey(settings.AUTH_USER_MODEL, null=False, blank=False,
|
||||
related_name="owned_projects", verbose_name=_("owner"))
|
||||
members = models.ManyToManyField(settings.AUTH_USER_MODEL, related_name="projects",
|
||||
through="Membership", verbose_name=_("members"))
|
||||
public = models.BooleanField(default=True, null=False, blank=True,
|
||||
verbose_name=_("public"))
|
||||
last_us_ref = models.BigIntegerField(null=True, blank=False, default=1,
|
||||
verbose_name=_("last us ref"))
|
||||
last_task_ref = models.BigIntegerField(null=True, blank=False, default=1,
|
||||
verbose_name=_("last task ref"))
|
||||
last_issue_ref = models.BigIntegerField(null=True, blank=False, default=1,
|
||||
verbose_name=_("last issue ref"))
|
||||
total_milestones = models.IntegerField(default=0, null=True, blank=True,
|
||||
verbose_name=_("total of milestones"))
|
||||
total_story_points = models.FloatField(default=None, null=True, blank=False,
|
||||
verbose_name=_("total story points"))
|
||||
tags = PickledObjectField(null=False, blank=True, verbose_name=_("tags"))
|
||||
|
||||
class ProjectDefaults(models.Model):
|
||||
default_points = models.OneToOneField("projects.Points", on_delete=models.SET_NULL,
|
||||
related_name="+", null=True, blank=True,
|
||||
verbose_name=_("default points"))
|
||||
|
@ -136,19 +82,53 @@ class Project(models.Model):
|
|||
related_name="+", null=True, blank=True,
|
||||
verbose_name=_("default questions "
|
||||
"status"))
|
||||
class Meta:
|
||||
abstract = True
|
||||
|
||||
|
||||
class Project(ProjectDefaults, models.Model):
|
||||
name = models.CharField(max_length=250, unique=True, null=False, blank=False,
|
||||
verbose_name=_("name"))
|
||||
slug = models.SlugField(max_length=250, unique=True, null=False, blank=True,
|
||||
verbose_name=_("slug"))
|
||||
description = models.TextField(null=False, blank=False,
|
||||
verbose_name=_("description"))
|
||||
created_date = models.DateTimeField(auto_now_add=True, null=False, blank=False,
|
||||
verbose_name=_("created date"))
|
||||
modified_date = models.DateTimeField(auto_now=True, null=False, blank=False,
|
||||
verbose_name=_("modified date"))
|
||||
owner = models.ForeignKey(settings.AUTH_USER_MODEL, null=False, blank=False,
|
||||
related_name="owned_projects", verbose_name=_("owner"))
|
||||
members = models.ManyToManyField(settings.AUTH_USER_MODEL, related_name="projects",
|
||||
through="Membership", verbose_name=_("members"))
|
||||
public = models.BooleanField(default=True, null=False, blank=True,
|
||||
verbose_name=_("public"))
|
||||
last_us_ref = models.BigIntegerField(null=True, blank=False, default=1,
|
||||
verbose_name=_("last us ref"))
|
||||
last_task_ref = models.BigIntegerField(null=True, blank=False, default=1,
|
||||
verbose_name=_("last task ref"))
|
||||
last_issue_ref = models.BigIntegerField(null=True, blank=False, default=1,
|
||||
verbose_name=_("last issue ref"))
|
||||
total_milestones = models.IntegerField(default=0, null=True, blank=True,
|
||||
verbose_name=_("total of milestones"))
|
||||
total_story_points = models.FloatField(default=None, null=True, blank=False,
|
||||
verbose_name=_("total story points"))
|
||||
tags = PickledObjectField(null=False, blank=True, verbose_name=_("tags"))
|
||||
site = models.ForeignKey("base.Site", related_name="projects", null=True, default=None)
|
||||
|
||||
notifiable_fields = [
|
||||
"name",
|
||||
"total_milestones",
|
||||
"total_story_points",
|
||||
"default_points",
|
||||
"default_us_status",
|
||||
"default_task_status",
|
||||
"default_priority",
|
||||
"default_severity",
|
||||
"default_issue_status",
|
||||
"default_issue_type",
|
||||
"default_question_status",
|
||||
# This realy should be in this list?
|
||||
# "default_points",
|
||||
# "default_us_status",
|
||||
# "default_task_status",
|
||||
# "default_priority",
|
||||
# "default_severity",
|
||||
# "default_issue_status",
|
||||
# "default_issue_type",
|
||||
# "default_question_status",
|
||||
"description"
|
||||
]
|
||||
|
||||
|
@ -178,9 +158,8 @@ class Project(models.Model):
|
|||
|
||||
def get_users(self):
|
||||
user_model = get_user_model()
|
||||
return user_model.objects.filter(
|
||||
id__in=list(self.memberships.values_list("user", flat=True))
|
||||
)
|
||||
members = self.memberships.values_list("user", flat=True)
|
||||
return user_model.objects.filter(id__in=list(members))
|
||||
|
||||
def update_role_points(self):
|
||||
rolepoints_model = get_model("userstories", "RolePoints")
|
||||
|
@ -251,6 +230,45 @@ class Project(models.Model):
|
|||
return self._get_user_stories_points(self.user_stories.filter(milestone__isnull=False))
|
||||
|
||||
|
||||
def get_attachment_file_path(instance, filename):
|
||||
return "attachment-files/{project}/{model}/{filename}".format(
|
||||
project=instance.project.slug,
|
||||
model=instance.content_type.model,
|
||||
filename=filename
|
||||
)
|
||||
|
||||
|
||||
class Attachment(models.Model):
|
||||
owner = models.ForeignKey(settings.AUTH_USER_MODEL, null=False, blank=False,
|
||||
related_name="change_attachments",
|
||||
verbose_name=_("owner"))
|
||||
project = models.ForeignKey("Project", null=False, blank=False,
|
||||
related_name="attachments", verbose_name=_("project"))
|
||||
content_type = models.ForeignKey(ContentType, null=False, blank=False,
|
||||
verbose_name=_("content type"))
|
||||
object_id = models.PositiveIntegerField(null=False, blank=False,
|
||||
verbose_name=_("object id"))
|
||||
content_object = generic.GenericForeignKey("content_type", "object_id")
|
||||
created_date = models.DateTimeField(auto_now_add=True, null=False, blank=False,
|
||||
verbose_name=_("created date"))
|
||||
modified_date = models.DateTimeField(auto_now=True, null=False, blank=False,
|
||||
verbose_name=_("modified date"))
|
||||
attached_file = models.FileField(max_length=500, null=True, blank=True,
|
||||
upload_to=get_attachment_file_path,
|
||||
verbose_name=_("attached file"))
|
||||
|
||||
class Meta:
|
||||
verbose_name = "attachment"
|
||||
verbose_name_plural = "attachments"
|
||||
ordering = ["project", "created_date"]
|
||||
permissions = (
|
||||
("view_attachment", "Can view attachment"),
|
||||
)
|
||||
|
||||
def __str__(self):
|
||||
return "Attachment: {}".format(self.id)
|
||||
|
||||
|
||||
# User Stories common Models
|
||||
class UserStoryStatus(models.Model):
|
||||
name = models.CharField(max_length=255, null=False, blank=False,
|
||||
|
|
|
@ -89,6 +89,12 @@ class QuestionStatusSerializer(serializers.ModelSerializer):
|
|||
# Projects
|
||||
|
||||
class MembershipSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = models.Membership
|
||||
read_only_fields = ("user",)
|
||||
|
||||
|
||||
class ProjectMembershipSerializer(serializers.ModelSerializer):
|
||||
role_name = serializers.CharField(source='role.name', required=False)
|
||||
full_name = serializers.CharField(source='user.get_full_name', required=False)
|
||||
|
||||
|
@ -101,14 +107,14 @@ class ProjectSerializer(serializers.ModelSerializer):
|
|||
|
||||
class Meta:
|
||||
model = models.Project
|
||||
read_only_fields = ("created_date", "modified_date", "owner")
|
||||
read_only_fields = ("created_date", "modified_date", "owner", "site")
|
||||
exclude = ("last_us_ref", "last_task_ref", "last_issue_ref")
|
||||
|
||||
|
||||
class ProjectDetailSerializer(ProjectSerializer):
|
||||
list_of_milestones = serializers.SerializerMethodField("get_list_of_milestones")
|
||||
roles = serializers.SerializerMethodField("get_list_of_roles")
|
||||
memberships = MembershipSerializer(many=True, required=False)
|
||||
memberships = ProjectMembershipSerializer(many=True, required=False)
|
||||
us_statuses = UserStoryStatusSerializer(many=True, required=False) # User Stories
|
||||
points = PointsSerializer(many=True, required=False)
|
||||
task_statuses = TaskStatusSerializer(many=True, required=False) # Tasks
|
||||
|
|
|
@ -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):
|
||||
model = get_model("users", "Role")
|
||||
roles = model.objects.filter(slug=role_slug)
|
||||
role = roles[0] if roles.exists() else model.objects.all()[0]
|
||||
role = model.objects.get(slug=role_slug)
|
||||
|
||||
model = get_model("projects", "Membership")
|
||||
instance = model.objects.create(
|
||||
|
|
|
@ -8,12 +8,12 @@ from django.core import mail
|
|||
from django.db.models import get_model
|
||||
|
||||
from greenmine.base.users.tests import create_user
|
||||
from greenmine.projects.models import Project
|
||||
from greenmine.projects.models import Project, Membership
|
||||
|
||||
from . import create_project, add_membership
|
||||
|
||||
class ProfileTestCase(test.TestCase):
|
||||
fixtures = ["initial_role.json", ]
|
||||
fixtures = ["initial_role.json", "initial_site.json"]
|
||||
|
||||
def setUp(self):
|
||||
self.user1 = create_user(1, is_superuser=True)
|
||||
|
@ -24,9 +24,10 @@ class ProfileTestCase(test.TestCase):
|
|||
self.project2 = create_project(2, self.user1)
|
||||
self.project3 = create_project(3, self.user2)
|
||||
|
||||
add_membership(self.project1, self.user3, "dev")
|
||||
add_membership(self.project3, self.user3, "dev")
|
||||
add_membership(self.project3, self.user2, "dev")
|
||||
add_membership(self.project1, self.user3, "back")
|
||||
add_membership(self.project3, self.user3, "back")
|
||||
add_membership(self.project3, self.user2, "back")
|
||||
|
||||
|
||||
def test_list_users(self):
|
||||
response = self.client.login(username=self.user3.username,
|
||||
|
@ -37,7 +38,7 @@ class ProfileTestCase(test.TestCase):
|
|||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
users_list = response.data
|
||||
self.assertEqual(len(users_list), 2)
|
||||
self.assertEqual(len(users_list), 3)
|
||||
|
||||
|
||||
def test_update_users(self):
|
||||
|
@ -153,19 +154,79 @@ class ProfileTestCase(test.TestCase):
|
|||
|
||||
|
||||
class ProjectsTestCase(test.TestCase):
|
||||
fixtures = ["initial_role.json", ]
|
||||
fixtures = ["initial_role.json", "initial_site.json"]
|
||||
|
||||
def setUp(self):
|
||||
self.user1 = create_user(1)
|
||||
self.user2 = create_user(2)
|
||||
self.user3 = create_user(3)
|
||||
self.user3 = create_user(4)
|
||||
|
||||
self.project1 = create_project(1, self.user1)
|
||||
self.project2 = create_project(2, self.user1)
|
||||
self.project3 = create_project(3, self.user2)
|
||||
self.project4 = create_project(4, self.user2)
|
||||
|
||||
add_membership(self.project1, self.user3, "dev")
|
||||
add_membership(self.project3, self.user3, "dev")
|
||||
add_membership(self.project1, self.user3, "back")
|
||||
add_membership(self.project3, self.user3, "back")
|
||||
|
||||
self.dev_role = get_model("users", "Role").objects.get(slug="back")
|
||||
|
||||
def test_send_invitations_01(self):
|
||||
response = self.client.login(username=self.user1.username,
|
||||
password=self.user1.username)
|
||||
self.assertTrue(response)
|
||||
|
||||
url = reverse("memberships-list")
|
||||
data = {"role": self.dev_role.id,
|
||||
"email": "pepe@pepe.com",
|
||||
"project": self.project4.id}
|
||||
|
||||
response = self.client.post(url, data=json.dumps(data), content_type="application/json")
|
||||
self.assertEqual(response.status_code, 201)
|
||||
self.assertEqual(self.project4.memberships.count(), 1)
|
||||
|
||||
self.assertEqual(len(mail.outbox), 1)
|
||||
self.assertNotEqual(len(mail.outbox[0].body), 0)
|
||||
|
||||
def test_send_invitations_02(self):
|
||||
response = self.client.login(username=self.user1.username,
|
||||
password=self.user1.username)
|
||||
self.assertTrue(response)
|
||||
|
||||
url = reverse("memberships-list")
|
||||
data = {"role": self.dev_role.id,
|
||||
"email": "pepe@pepe.com",
|
||||
"project": self.project4.id}
|
||||
|
||||
response = self.client.post(url, data=json.dumps(data), content_type="application/json")
|
||||
|
||||
self.assertEqual(response.status_code, 201)
|
||||
self.assertEqual(self.project4.memberships.count(), 1)
|
||||
self.assertEqual(len(mail.outbox), 1)
|
||||
self.assertNotEqual(len(mail.outbox[0].body), 0)
|
||||
|
||||
response = self.client.post(url, data=json.dumps(data), content_type="application/json")
|
||||
|
||||
self.assertEqual(self.project4.memberships.count(), 1)
|
||||
self.assertEqual(response.status_code, 400)
|
||||
self.assertEqual(len(mail.outbox), 1)
|
||||
self.assertNotEqual(len(mail.outbox[0].body), 0)
|
||||
|
||||
def test_send_invitations_03(self):
|
||||
response = self.client.login(username=self.user1.username,
|
||||
password=self.user1.username)
|
||||
self.assertTrue(response)
|
||||
|
||||
url = reverse("memberships-list")
|
||||
data = {"role": self.dev_role.id,
|
||||
"email": self.user3.email,
|
||||
"project": self.project3.id}
|
||||
|
||||
response = self.client.post(url, data=json.dumps(data), content_type="application/json")
|
||||
self.assertEqual(response.status_code, 400)
|
||||
|
||||
self.assertEqual(len(mail.outbox), 0)
|
||||
|
||||
def test_list_projects_by_anon(self):
|
||||
response = self.client.get(reverse("projects-list"))
|
||||
|
@ -187,7 +248,7 @@ class ProjectsTestCase(test.TestCase):
|
|||
response = self.client.get(reverse("projects-list"))
|
||||
self.assertEqual(response.status_code, 200)
|
||||
projects_list = response.data
|
||||
self.assertEqual(len(projects_list), 1)
|
||||
self.assertEqual(len(projects_list), 2)
|
||||
self.client.logout()
|
||||
|
||||
def test_list_projects_by_membership(self):
|
||||
|
@ -239,13 +300,13 @@ class ProjectsTestCase(test.TestCase):
|
|||
"total_story_points": 10
|
||||
}
|
||||
|
||||
self.assertEqual(Project.objects.all().count(), 3)
|
||||
self.assertEqual(Project.objects.all().count(), 4)
|
||||
response = self.client.post(
|
||||
reverse("projects-list"),
|
||||
json.dumps(data),
|
||||
content_type="application/json")
|
||||
self.assertEqual(response.status_code, 401)
|
||||
self.assertEqual(Project.objects.all().count(), 3)
|
||||
self.assertEqual(Project.objects.all().count(), 4)
|
||||
|
||||
def test_create_project_by_auth(self):
|
||||
data = {
|
||||
|
@ -254,16 +315,15 @@ class ProjectsTestCase(test.TestCase):
|
|||
"total_story_points": 10
|
||||
}
|
||||
|
||||
self.assertEqual(Project.objects.all().count(), 3)
|
||||
self.assertEqual(Project.objects.all().count(), 4)
|
||||
response = self.client.login(username=self.user1.username,
|
||||
password=self.user1.username)
|
||||
self.assertTrue(response)
|
||||
response = self.client.post(
|
||||
reverse("projects-list"),
|
||||
json.dumps(data),
|
||||
response = self.client.post(reverse("projects-list"), json.dumps(data),
|
||||
content_type="application/json")
|
||||
|
||||
self.assertEqual(response.status_code, 201)
|
||||
self.assertEqual(Project.objects.all().count(), 4)
|
||||
self.assertEqual(Project.objects.all().count(), 5)
|
||||
self.client.logout()
|
||||
|
||||
def test_edit_project_by_anon(self):
|
||||
|
@ -271,21 +331,21 @@ class ProjectsTestCase(test.TestCase):
|
|||
"description": "Edited project description",
|
||||
}
|
||||
|
||||
self.assertEqual(Project.objects.all().count(), 3)
|
||||
self.assertEqual(Project.objects.all().count(), 4)
|
||||
self.assertNotEqual(data["description"], self.project1.description)
|
||||
response = self.client.patch(
|
||||
reverse("projects-detail", args=(self.project1.id,)),
|
||||
json.dumps(data),
|
||||
content_type="application/json")
|
||||
self.assertEqual(response.status_code, 401)
|
||||
self.assertEqual(Project.objects.all().count(), 3)
|
||||
self.assertEqual(Project.objects.all().count(), 4)
|
||||
|
||||
def test_edit_project_by_owner(self):
|
||||
data = {
|
||||
"description": "Modified project description",
|
||||
}
|
||||
|
||||
self.assertEqual(Project.objects.all().count(), 3)
|
||||
self.assertEqual(Project.objects.all().count(), 4)
|
||||
self.assertNotEqual(data["description"], self.project1.description)
|
||||
response = self.client.login(username=self.user1.username,
|
||||
password=self.user1.username)
|
||||
|
@ -296,7 +356,7 @@ class ProjectsTestCase(test.TestCase):
|
|||
content_type="application/json")
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertEqual(data["description"], response.data["description"])
|
||||
self.assertEqual(Project.objects.all().count(), 3)
|
||||
self.assertEqual(Project.objects.all().count(), 4)
|
||||
self.client.logout()
|
||||
|
||||
def test_edit_project_by_membership(self):
|
||||
|
@ -304,7 +364,7 @@ class ProjectsTestCase(test.TestCase):
|
|||
"description": "Edited project description",
|
||||
}
|
||||
|
||||
self.assertEqual(Project.objects.all().count(), 3)
|
||||
self.assertEqual(Project.objects.all().count(), 4)
|
||||
self.assertNotEqual(data["description"], self.project1.description)
|
||||
response = self.client.login(username=self.user3.username,
|
||||
password=self.user3.username)
|
||||
|
@ -315,7 +375,7 @@ class ProjectsTestCase(test.TestCase):
|
|||
content_type="application/json")
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertEqual(data["description"], response.data["description"])
|
||||
self.assertEqual(Project.objects.all().count(), 3)
|
||||
self.assertEqual(Project.objects.all().count(), 4)
|
||||
self.client.logout()
|
||||
|
||||
def test_edit_project_by_not_membership(self):
|
||||
|
@ -323,7 +383,7 @@ class ProjectsTestCase(test.TestCase):
|
|||
"description": "Edited project description",
|
||||
}
|
||||
|
||||
self.assertEqual(Project.objects.all().count(), 3)
|
||||
self.assertEqual(Project.objects.all().count(), 4)
|
||||
self.assertNotEqual(data["description"], self.project1.description)
|
||||
response = self.client.login(username=self.user2.username,
|
||||
password=self.user2.username)
|
||||
|
@ -333,41 +393,41 @@ class ProjectsTestCase(test.TestCase):
|
|||
json.dumps(data),
|
||||
content_type="application/json")
|
||||
self.assertEqual(response.status_code, 404)
|
||||
self.assertEqual(Project.objects.all().count(), 3)
|
||||
self.assertEqual(Project.objects.all().count(), 4)
|
||||
self.client.logout()
|
||||
|
||||
def test_delete_project_by_anon(self):
|
||||
self.assertEqual(Project.objects.all().count(), 3)
|
||||
self.assertEqual(Project.objects.all().count(), 4)
|
||||
response = self.client.delete(reverse("projects-detail", args=(self.project1.id,)))
|
||||
self.assertEqual(response.status_code, 401)
|
||||
self.assertEqual(Project.objects.all().count(), 3)
|
||||
self.assertEqual(Project.objects.all().count(), 4)
|
||||
|
||||
def test_delete_project_by_owner(self):
|
||||
self.assertEqual(Project.objects.all().count(), 3)
|
||||
self.assertEqual(Project.objects.all().count(), 4)
|
||||
response = self.client.login(username=self.user1.username,
|
||||
password=self.user1.username)
|
||||
self.assertTrue(response)
|
||||
response = self.client.delete(reverse("projects-detail", args=(self.project1.id,)))
|
||||
self.assertEqual(response.status_code, 204)
|
||||
self.assertEqual(Project.objects.all().count(), 2)
|
||||
self.assertEqual(Project.objects.all().count(), 3)
|
||||
self.client.logout()
|
||||
|
||||
def test_delete_project_by_membership(self):
|
||||
self.assertEqual(Project.objects.all().count(), 3)
|
||||
self.assertEqual(Project.objects.all().count(), 4)
|
||||
response = self.client.login(username=self.user3.username,
|
||||
password=self.user3.username)
|
||||
self.assertTrue(response)
|
||||
response = self.client.delete(reverse("projects-detail", args=(self.project1.id,)))
|
||||
self.assertEqual(response.status_code, 204)
|
||||
self.assertEqual(Project.objects.all().count(), 2)
|
||||
self.assertEqual(Project.objects.all().count(), 3)
|
||||
self.client.logout()
|
||||
|
||||
def test_delete_project_by_not_membership(self):
|
||||
self.assertEqual(Project.objects.all().count(), 3)
|
||||
self.assertEqual(Project.objects.all().count(), 4)
|
||||
response = self.client.login(username=self.user1.username,
|
||||
password=self.user1.username)
|
||||
self.assertTrue(response)
|
||||
response = self.client.delete(reverse("projects-detail", args=(self.project3.id,)))
|
||||
self.assertEqual(response.status_code, 404)
|
||||
self.assertEqual(Project.objects.all().count(), 3)
|
||||
self.assertEqual(Project.objects.all().count(), 4)
|
||||
self.client.logout()
|
||||
|
|
|
@ -4,7 +4,7 @@ from greenmine.base import routers
|
|||
from greenmine.base.auth.api import AuthViewSet
|
||||
from greenmine.base.users.api import RolesViewSet, UsersViewSet
|
||||
from greenmine.base.searches.api import SearchViewSet
|
||||
from greenmine.projects.api import ProjectViewSet, MembershipViewSet
|
||||
from greenmine.projects.api import ProjectViewSet, MembershipViewSet, InvitationViewSet
|
||||
from greenmine.projects.milestones.api import MilestoneViewSet
|
||||
from greenmine.projects.userstories.api import UserStoryViewSet, UserStoryAttachmentViewSet
|
||||
from greenmine.projects.tasks.api import TaskViewSet, TaskAttachmentViewSet
|
||||
|
@ -27,6 +27,7 @@ router.register(r"search", SearchViewSet, base_name="search")
|
|||
# greenmine.projects
|
||||
router.register(r"projects", ProjectViewSet, base_name="projects")
|
||||
router.register(r"memberships", MembershipViewSet, base_name="memberships")
|
||||
router.register(r"invitations", InvitationViewSet, base_name="invitations")
|
||||
|
||||
# greenmine.projects.milestones
|
||||
router.register(r"milestones", MilestoneViewSet, base_name="milestones")
|
||||
|
|
|
@ -15,6 +15,8 @@ OUT_PROJECT_ROOT = os.path.abspath(
|
|||
os.path.join(PROJECT_ROOT, "..")
|
||||
)
|
||||
|
||||
USE_X_FORWARDED_HOST = True
|
||||
|
||||
APPEND_SLASH = False
|
||||
|
||||
|
||||
|
@ -151,17 +153,17 @@ TEMPLATE_LOADERS = [
|
|||
]
|
||||
|
||||
MIDDLEWARE_CLASSES = [
|
||||
'greenmine.base.middleware.CoorsMiddleware',
|
||||
'greenmine.base.middleware.SitesMiddleware',
|
||||
|
||||
# Common middlewares
|
||||
'django.middleware.common.CommonMiddleware',
|
||||
'django.middleware.locale.LocaleMiddleware',
|
||||
'greenmine.base.middleware.CoorsMiddleware',
|
||||
|
||||
# Only needed by django admin
|
||||
'django.contrib.sessions.middleware.SessionMiddleware',
|
||||
'django.contrib.auth.middleware.AuthenticationMiddleware',
|
||||
'django.contrib.messages.middleware.MessageMiddleware',
|
||||
|
||||
# 'greenmine.base.middleware.GreenmineSessionMiddleware',
|
||||
]
|
||||
|
||||
TEMPLATE_CONTEXT_PROCESSORS = [
|
||||
|
@ -182,9 +184,6 @@ TEMPLATE_DIRS = [
|
|||
]
|
||||
|
||||
INSTALLED_APPS = [
|
||||
# 'grappelli.dashboard',
|
||||
# 'grappelli',
|
||||
|
||||
'django.contrib.auth',
|
||||
'django.contrib.contenttypes',
|
||||
'django.contrib.sessions',
|
||||
|
@ -192,10 +191,10 @@ INSTALLED_APPS = [
|
|||
'django.contrib.admin',
|
||||
'django.contrib.staticfiles',
|
||||
|
||||
'greenmine.base',
|
||||
'greenmine.base.notifications',
|
||||
'greenmine.base.users',
|
||||
'greenmine.base.notifications',
|
||||
'greenmine.base.searches',
|
||||
'greenmine.base',
|
||||
'greenmine.projects',
|
||||
'greenmine.projects.milestones',
|
||||
'greenmine.projects.userstories',
|
||||
|
@ -210,7 +209,6 @@ INSTALLED_APPS = [
|
|||
'reversion',
|
||||
'rest_framework',
|
||||
'djmail',
|
||||
'django_sites',
|
||||
]
|
||||
|
||||
WSGI_APPLICATION = 'greenmine.wsgi.application'
|
||||
|
@ -224,9 +222,12 @@ LOGGING = {
|
|||
}
|
||||
},
|
||||
'formatters': {
|
||||
'simple': {
|
||||
'complete': {
|
||||
'format': '%(levelname)s:%(asctime)s:%(module)s %(message)s'
|
||||
},
|
||||
'simple': {
|
||||
'format': '%(levelname)s:%(asctime)s: %(message)s'
|
||||
},
|
||||
'null': {
|
||||
'format': '%(message)s',
|
||||
},
|
||||
|
@ -239,7 +240,7 @@ LOGGING = {
|
|||
'console':{
|
||||
'level':'DEBUG',
|
||||
'class':'logging.StreamHandler',
|
||||
'formatter': 'null',
|
||||
'formatter': 'simple',
|
||||
},
|
||||
'mail_admins': {
|
||||
'level': 'ERROR',
|
||||
|
@ -258,11 +259,16 @@ LOGGING = {
|
|||
'level': 'ERROR',
|
||||
'propagate': False,
|
||||
},
|
||||
'main': {
|
||||
'greenmine': {
|
||||
'handlers': ['console'],
|
||||
'level': 'DEBUG',
|
||||
'propagate': False,
|
||||
}
|
||||
},
|
||||
'greenmine.site': {
|
||||
'handlers': ['console'],
|
||||
'level': 'DEBUG',
|
||||
'propagate': False,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -291,6 +297,7 @@ REST_FRAMEWORK = {
|
|||
'greenmine.base.auth.Session',
|
||||
),
|
||||
'FILTER_BACKEND': 'greenmine.base.filters.FilterBackend',
|
||||
'EXCEPTION_HANDLER': 'greenmine.base.exceptions.exception_handler',
|
||||
'PAGINATE_BY': 30,
|
||||
'MAX_PAGINATE_BY': 1000,
|
||||
}
|
||||
|
|
|
@ -13,6 +13,7 @@ admin.autodiscover()
|
|||
urlpatterns = patterns('',
|
||||
url(r'^api/v1/', include(router.urls)),
|
||||
url(r'^api/v1/api-auth/', include('rest_framework.urls', namespace='rest_framework')),
|
||||
url(r'^api/v1/sites', "greenmine.base.apiviews.sitestatus"),
|
||||
url(r'^admin/', include(admin.site.urls)),
|
||||
url(r'^grappelli/', include('grappelli.urls')),
|
||||
)
|
||||
|
|
|
@ -8,6 +8,8 @@ createdb greenmine
|
|||
|
||||
echo "-> Run syncdb"
|
||||
python manage.py syncdb --migrate --noinput --traceback
|
||||
# echo "-> Load initial Site"
|
||||
# python manage.py loaddata initial_site --traceback
|
||||
echo "-> Load initial user"
|
||||
python manage.py loaddata initial_user --traceback
|
||||
echo "-> Load initial roles"
|
||||
|
|
|
@ -2,7 +2,7 @@ git+https://github.com/tomchristie/django-rest-framework.git@2.4.0
|
|||
git+https://github.com/etianen/django-reversion.git@django-1.6
|
||||
|
||||
Django==1.6.0
|
||||
South==0.8.2
|
||||
South==0.8.3
|
||||
anyjson==0.3.3
|
||||
Werkzeug==0.9.4
|
||||
celery==3.0.24
|
||||
|
@ -19,4 +19,3 @@ djmail>=0.4
|
|||
django-jinja>=0.21
|
||||
jinja2==2.7.1
|
||||
pygments>=1.6
|
||||
django-sites>=0.4
|
||||
|
|
Loading…
Reference in New Issue