Merge pull request #42 from taigaio/sites-remove

Sites remove & Test refactor
remotes/origin/enhancement/email-actions
David Barragán Merino 2014-05-26 17:43:39 +02:00
commit a1c1491a84
41 changed files with 438 additions and 1629 deletions

1
.gitignore vendored
View File

@ -9,3 +9,4 @@ media
*.mo
.venv
.coverage
.\#*

View File

@ -11,8 +11,6 @@ python manage.py syncdb --all --noinput --traceback
python manage.py migrate --fake
# echo "-> Load initial Site"
# python manage.py loaddata initial_site --traceback
echo "-> Load initial domain"
python manage.py loaddata initial_domains --traceback
echo "-> Load initial user"
python manage.py loaddata initial_user --traceback
echo "-> Load initial project_templates"

View File

@ -12,10 +12,10 @@ pytz>=2013.9
six>=1.4.1
djmail>=0.4
django-pgjson==0.1.2
django-jinja>=0.23
django-jinja>=1.0.1
jinja2==2.7.1
pygments>=1.6
django-sites==0.4
django-sites==0.6
Markdown==2.4
fn==0.2.13
diff-match-patch==20110725.1

View File

@ -73,11 +73,11 @@ LOGIN_URL="/auth/login/"
USE_TZ = True
SITES = {
1: {"domain": "localhost:8000", "scheme": "http"},
"api": {"domain": "localhost:8000", "scheme": "http", "name": "api"},
"front": {"domain": "localhost:9001", "scheme": "http", "name": "front"},
}
DOMAIN_ID = 1
SITE_ID = 1
SITE_ID = "api"
# Session configuration (only used for admin)
SESSION_ENGINE="django.contrib.sessions.backends.db"
@ -133,7 +133,6 @@ TEMPLATE_LOADERS = [
MIDDLEWARE_CLASSES = [
"taiga.base.middleware.cors.CoorsMiddleware",
"taiga.domains.middleware.DomainsMiddleware",
"taiga.events.middleware.SessionIDMiddleware",
# Common middlewares
@ -174,7 +173,6 @@ INSTALLED_APPS = [
"taiga.base",
"taiga.base.searches",
"taiga.events",
"taiga.domains",
"taiga.front",
"taiga.users",
"taiga.userstorage",
@ -294,6 +292,7 @@ REST_FRAMEWORK = {
}
DEFAULT_PROJECT_TEMPLATE = "scrum"
PUBLIC_REGISTER_ENABLED = False
# NOTE: DON'T INSERT MORE SETTINGS AFTER THIS LINE

View File

@ -18,6 +18,7 @@ from functools import partial
from enum import Enum
from django.utils.translation import ugettext_lazy as _
from django.conf import settings
from rest_framework.response import Response
from rest_framework.permissions import AllowAny
@ -28,7 +29,6 @@ from rest_framework import serializers
from taiga.base.decorators import list_route
from taiga.base import exceptions as exc
from taiga.users.services import get_and_validate_user
from taiga.domains.services import is_public_register_enabled_for_domain
from .serializers import PublicRegisterSerializer
from .serializers import PrivateRegisterForExistingUserSerializer
@ -96,16 +96,16 @@ class AuthViewSet(viewsets.ViewSet):
permission_classes = (AllowAny,)
def _public_register(self, request):
if not is_public_register_enabled_for_domain(request.domain):
raise exc.BadRequest(_("Public register is disabled for this domain."))
if not settings.PUBLIC_REGISTER_ENABLED:
raise exc.BadRequest(_("Public register is disabled."))
try:
data = parse_public_register_data(request.DATA)
user = public_register(request.domain, **data)
user = public_register(**data)
except exc.IntegrityError as e:
raise exc.BadRequest(e.detail)
data = make_auth_response_data(request.domain, user)
data = make_auth_response_data(user)
return Response(data, status=status.HTTP_201_CREATED)
def _private_register(self, request):
@ -113,12 +113,12 @@ class AuthViewSet(viewsets.ViewSet):
if register_type is RegisterTypeEnum.existing_user:
data = parse_private_register_for_existing_user_data(request.DATA)
user = private_register_for_existing_user(request.domain, **data)
user = private_register_for_existing_user(**data)
else:
data = parse_private_register_for_new_user_data(request.DATA)
user = private_register_for_new_user(request.domain, **data)
user = private_register_for_new_user(**data)
data = make_auth_response_data(request.domain, user)
data = make_auth_response_data(user)
return Response(data, status=status.HTTP_201_CREATED)
@list_route(methods=["POST"], permission_classes=[AllowAny])
@ -136,5 +136,5 @@ class AuthViewSet(viewsets.ViewSet):
password = request.DATA.get('password', None)
user = get_and_validate_user(username=username, password=password)
data = make_auth_response_data(request.domain, user)
data = make_auth_response_data(user)
return Response(data, status=status.HTTP_200_OK)

View File

@ -34,8 +34,6 @@ from djmail.template_mail import MagicMailBuilder
from taiga.base import exceptions as exc
from taiga.users.serializers import UserSerializer
from taiga.users.services import get_and_validate_user
from taiga.domains.services import (create_domain_member,
is_user_exists_on_domain)
from .backends import get_token_for_user
@ -92,8 +90,7 @@ def get_membership_by_token(token:str):
@tx.atomic
def public_register(domain, *, username:str, password:str,
email:str, first_name:str, last_name:str):
def public_register(username:str, password:str, email:str, first_name:str, last_name:str):
"""
Given a parsed parameters, try register a new user
knowing that it follows a public register flow.
@ -115,15 +112,12 @@ def public_register(domain, *, username:str, password:str,
user.set_password(password)
user.save()
if not is_user_exists_on_domain(domain, user):
create_domain_member(domain, user)
# send_public_register_email(user)
return user
@tx.atomic
def private_register_for_existing_user(domain, *, token:str, username:str, password:str):
def private_register_for_existing_user(token:str, username:str, password:str):
"""
Register works not only for register users, also serves for accept
inviatations for projects as existing user.
@ -135,9 +129,6 @@ def private_register_for_existing_user(domain, *, token:str, username:str, passw
user = get_and_validate_user(username=username, password=password)
membership = get_membership_by_token(token)
if not is_user_exists_on_domain(domain, user):
create_domain_member(domain, user)
membership.user = user
membership.save(update_fields=["user"])
@ -146,7 +137,7 @@ def private_register_for_existing_user(domain, *, token:str, username:str, passw
@tx.atomic
def private_register_for_new_user(domain, *, token:str, username:str, email:str,
def private_register_for_new_user(token:str, username:str, email:str,
first_name:str, last_name:str, password:str):
"""
Given a inviation token, try register new user matching
@ -169,9 +160,6 @@ def private_register_for_new_user(domain, *, token:str, username:str, email:str,
except IntegrityError:
raise exc.IntegrityError(_("Error on creating new user."))
if not is_user_exists_on_domain(domain, user):
create_domain_member(domain, user)
membership = get_membership_by_token(token)
membership.user = user
membership.save(update_fields=["user"])
@ -179,7 +167,7 @@ def private_register_for_new_user(domain, *, token:str, username:str, email:str,
return user
def make_auth_response_data(domain, user) -> dict:
def make_auth_response_data(user) -> dict:
"""
Given a domain and user, creates data structure
using python dict containing a representation
@ -187,9 +175,5 @@ def make_auth_response_data(domain, user) -> dict:
"""
serializer = UserSerializer(user)
data = dict(serializer.data)
data['is_site_owner'] = domain.user_is_owner(user)
data['is_site_staff'] = domain.user_is_staff(user)
data["auth_token"] = get_token_for_user(user)
return data

View File

@ -18,8 +18,6 @@ from django.forms import widgets
from rest_framework import serializers
from taiga.domains.base import get_active_domain
from taiga.domains.models import Domain
from .neighbors import get_neighbors
@ -47,20 +45,6 @@ class JsonField(serializers.WritableField):
return data
class AutoDomainField(serializers.WritableField):
"""
Automatically set domain field serializer.
"""
def to_native(self, obj):
if obj:
return obj.id
return obj
def from_native(self, data):
domain = get_active_domain()
return domain
class NeighborsSerializerMixin:
def __init__(self, *args, **kwargs):

View File

@ -1,27 +0,0 @@
# Copyright (C) 2014 Andrey Antukh <niwi@niwi.be>
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from .base import get_default_domain
from .base import get_domain_for_domain_name
from .base import activate
from .base import deactivate
from .base import get_active_domain
from .base import DomainNotFound
__all__ = ["get_default_domain",
"get_domain_for_domain_name",
"activate",
"deactivate",
"get_active_domain",
"DomainNotFound"]

View File

@ -1,27 +0,0 @@
# Copyright (C) 2014 Andrey Antukh <niwi@niwi.be>
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from django.contrib import admin
from .models import Domain, DomainMember
class DomainMemberInline(admin.TabularInline):
model = DomainMember
class DomainAdmin(admin.ModelAdmin):
list_display = ('domain', 'name')
search_fields = ('domain', 'name')
inlines = [ DomainMemberInline, ]
admin.site.register(Domain, DomainAdmin)

View File

@ -1,64 +0,0 @@
# Copyright (C) 2014 Andrey Antukh <niwi@niwi.be>
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from rest_framework.response import Response
from rest_framework.permissions import IsAuthenticated
from taiga.base.api import GenericViewSet
from taiga.base.api import ListModelMixin
from taiga.base.api import UpdateModelMixin
from taiga.base import exceptions as exc
from .base import get_active_domain
from .serializers import DomainSerializer
from .serializers import DomainMemberSerializer
from .permissions import DomainPermission
from .permissions import DomainMembersPermission
from .models import Domain
from .models import DomainMember
class DomainViewSet(UpdateModelMixin, GenericViewSet):
permission_classes = (DomainPermission,)
serializer_class = DomainSerializer
queryset = Domain.objects.all()
def list(self, request, **kwargs):
domain_data = DomainSerializer(request.domain).data
if request.domain.user_is_normal_user(request.user):
domain_data['projects'] = None
elif request.user.is_anonymous():
domain_data['projects'] = None
return Response(domain_data)
def update(self, request, *args, **kwargs):
raise exc.NotSupported()
def partial_update(self, request, *args, **kwargs):
raise exc.NotSupported()
def create(self, request, **kwargs):
self.kwargs['pk'] = request.domain.pk
return super().update(request, pk=request.domain.pk, **kwargs)
class DomainMembersViewSet(ListModelMixin, UpdateModelMixin, GenericViewSet):
permission_classes = (IsAuthenticated, DomainMembersPermission,)
serializer_class = DomainMemberSerializer
queryset = DomainMember.objects.all()
def get_queryset(self):
domain = get_active_domain()
qs = super().get_queryset()
return qs.filter(domain=domain).distinct()

View File

@ -1,87 +0,0 @@
# Copyright (C) 2014 Andrey Antukh <niwi@niwi.be>
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import logging
import functools
import threading
from django.db.models import get_model
from django.core.exceptions import ImproperlyConfigured
from django.utils.translation import ugettext_lazy as _
from taiga.base import exceptions as exc
log = logging.getLogger("taiga.domains")
_local = threading.local()
class DomainNotFound(exc.BaseException):
pass
@functools.lru_cache(maxsize=1)
def get_default_domain():
from django.conf import settings
try:
sid = settings.DOMAIN_ID
except AttributeError:
raise ImproperlyConfigured("You're using the \"domains framework\" without having "
"set the DOMAIN_ID setting. Create a domain in your database "
"and set the DOMAIN_ID setting to fix this error.")
model_cls = get_model("domains", "Domain")
try:
return model_cls.objects.get(pk=sid)
except model_cls.DoesNotExist:
raise ImproperlyConfigured("default domain not found on database.")
@functools.lru_cache(maxsize=100, typed=True)
def get_domain_for_domain_name(domain:str, follow_alias:bool=True):
log.debug("Trying activate domain for domain name: {}".format(domain))
model_cls = get_model("domains", "Domain")
try:
domain = model_cls.objects.get(domain=domain)
except model_cls.DoesNotExist:
log.warning("Domain does not exist for domain: {}".format(domain))
raise DomainNotFound(_("domain not found"))
# Use `alias_of_id` instead of simple `alias_of` for performace reasons.
if domain.alias_of_id is None or not follow_alias:
return domain
return domain.alias_of
def activate(domain):
log.debug("Activating domain: {}".format(domain))
_local.active_domain = domain
def deactivate():
if hasattr(_local, "active_domain"):
log.debug("Deactivating domain: {}".format(_local.active_domain))
del _local.active_domain
def get_active_domain():
active_domain = getattr(_local, "active_domain", None)
if active_domain is None:
return get_default_domain()
return active_domain
def clear_domain_cache(**kwargs):
get_default_domain.cache_clear()
get_domain_for_domain_name.cache_clear()

View File

@ -1,12 +0,0 @@
[
{
"model": "domains.domain",
"fields": {
"public_register": false,
"domain": "localhost",
"scheme": null,
"name": "localhost"
},
"pk": 1
}
]

View File

@ -1,54 +0,0 @@
# Copyright (C) 2014 Andrey Antukh <niwi@niwi.be>
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import json
from django import http
from taiga.base.exceptions import format_exception
from .base import get_domain_for_domain_name
from .base import activate as activate_domain
from .base import deactivate as deactivate_domain
from .base import get_default_domain
from .base import DomainNotFound
class DomainsMiddleware(object):
"""
Domain middlewate: process request and try resolve domain
from HTTP_X_HOST header. If no header is specified, one
default is used.
"""
def process_request(self, request):
domain = request.META.get("HTTP_X_HOST", None)
if domain is not None:
try:
domain = get_domain_for_domain_name(domain, follow_alias=True)
except DomainNotFound as e:
detail = format_exception(e)
return http.HttpResponseBadRequest(json.dumps(detail))
else:
domain = get_default_domain()
request.domain = domain
activate_domain(domain)
def process_response(self, request, response):
deactivate_domain()
if hasattr(request, "domain"):
response["X-Site-Host"] = request.domain.domain
return response

View File

@ -1,112 +0,0 @@
# -*- coding: utf-8 -*-
from south.utils import datetime_utils as 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 'Domain'
db.create_table('domains_domain', (
('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
('domain', self.gf('django.db.models.fields.CharField')(max_length=255, unique=True)),
('name', self.gf('django.db.models.fields.CharField')(max_length=255)),
('scheme', self.gf('django.db.models.fields.CharField')(max_length=60, null=True, default=None)),
('public_register', self.gf('django.db.models.fields.BooleanField')(default=False)),
))
db.send_create_signal('domains', ['Domain'])
# Adding model 'DomainMember'
db.create_table('domains_domainmember', (
('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
('site', self.gf('django.db.models.fields.related.ForeignKey')(related_name='+', to=orm['domains.Domain'])),
('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('domains', ['DomainMember'])
# Adding unique constraint on 'DomainMember', fields ['site', 'user']
db.create_unique('domains_domainmember', ['site_id', 'user_id'])
def backwards(self, orm):
# Removing unique constraint on 'DomainMember', fields ['site', 'user']
db.delete_unique('domains_domainmember', ['site_id', 'user_id'])
# Deleting model 'Domain'
db.delete_table('domains_domain')
# Deleting model 'DomainMember'
db.delete_table('domains_domainmember')
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', [], {'to': "orm['auth.Permission']", 'blank': 'True', 'symmetrical': 'False'})
},
'auth.permission': {
'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'object_name': 'Permission', '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'})
},
'contenttypes.contenttype': {
'Meta': {'ordering': "('name',)", 'db_table': "'django_content_type'", 'object_name': 'ContentType', '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'})
},
'domains.domain': {
'Meta': {'ordering': "('domain',)", 'object_name': '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', 'null': 'True', 'default': 'None'})
},
'domains.domainmember': {
'Meta': {'ordering': "['email']", 'object_name': 'DomainMember', '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['domains.Domain']"}),
'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'+'", 'null': 'True', 'to': "orm['users.User']"})
},
'users.user': {
'Meta': {'ordering': "['username']", 'object_name': 'User'},
'color': ('django.db.models.fields.CharField', [], {'max_length': '9', 'blank': 'True', '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', [], {'max_length': '20', 'blank': 'True', 'default': "''"}),
'default_timezone': ('django.db.models.fields.CharField', [], {'max_length': '20', 'blank': 'True', 'default': "''"}),
'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', [], {'to': "orm['auth.Group']", 'related_name': "'user_set'", 'blank': 'True', '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', [], {'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', 'null': 'True', 'default': 'None'}),
'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'related_name': "'user_set'", 'blank': 'True', 'symmetrical': 'False'}),
'username': ('django.db.models.fields.CharField', [], {'max_length': '30', 'unique': 'True'})
}
}
complete_apps = ['domains']

View File

@ -1,98 +0,0 @@
# -*- coding: utf-8 -*-
from south.utils import datetime_utils as datetime
from south.db import db
from south.v2 import SchemaMigration
from django.db import models
class Migration(SchemaMigration):
def forwards(self, orm):
# Adding field 'DomainMember.domain'
db.add_column('domains_domainmember', 'domain',
self.gf('django.db.models.fields.related.ForeignKey')(to=orm['domains.Domain'], null=True, related_name='+'),
keep_default=False)
# Changing field 'DomainMember.site'
db.alter_column('domains_domainmember', 'site_id', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['domains.Domain'], null=True))
def backwards(self, orm):
# Deleting field 'DomainMember.domain'
db.delete_column('domains_domainmember', 'domain_id')
# User chose to not deal with backwards NULL issues for 'DomainMember.site'
raise RuntimeError("Cannot reverse this migration. 'DomainMember.site' and its values cannot be restored.")
# The following code is provided here to aid in writing a correct migration
# Changing field 'DomainMember.site'
db.alter_column('domains_domainmember', 'site_id', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['domains.Domain']))
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', [], {'to': "orm['auth.Permission']", 'blank': 'True', 'symmetrical': 'False'})
},
'auth.permission': {
'Meta': {'unique_together': "(('content_type', 'codename'),)", 'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'object_name': 'Permission'},
'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'})
},
'contenttypes.contenttype': {
'Meta': {'db_table': "'django_content_type'", 'unique_together': "(('app_label', 'model'),)", 'ordering': "('name',)", 'object_name': 'ContentType'},
'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'})
},
'domains.domain': {
'Meta': {'ordering': "('domain',)", 'object_name': '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', [], {'null': 'True', 'default': 'None', 'max_length': '60'})
},
'domains.domainmember': {
'Meta': {'unique_together': "(('site', 'user'),)", 'ordering': "['email']", 'object_name': 'DomainMember'},
'domain': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['domains.Domain']", 'null': 'True', 'related_name': "'+'"}),
'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', [], {'to': "orm['domains.Domain']", 'null': 'True', 'related_name': "'+'"}),
'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['users.User']", 'null': 'True', 'related_name': "'+'"})
},
'users.user': {
'Meta': {'ordering': "['username']", 'object_name': 'User'},
'color': ('django.db.models.fields.CharField', [], {'blank': 'True', 'default': "'#669933'", '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', [], {'blank': 'True', 'default': "''", 'max_length': '20'}),
'default_timezone': ('django.db.models.fields.CharField', [], {'blank': 'True', 'default': "''", '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', [], {'to': "orm['auth.Group']", 'related_name': "'user_set'", 'blank': 'True', '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', [], {'default': "'all_owned_projects'", 'max_length': '32'}),
'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
'photo': ('django.db.models.fields.files.FileField', [], {'null': 'True', 'blank': 'True', 'max_length': '500'}),
'token': ('django.db.models.fields.CharField', [], {'null': 'True', 'default': 'None', 'max_length': '200', 'blank': 'True'}),
'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'related_name': "'user_set'", 'blank': 'True', 'symmetrical': 'False'}),
'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
}
}
complete_apps = ['domains']

View File

@ -1,89 +0,0 @@
# -*- coding: utf-8 -*-
from south.utils import datetime_utils as 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.
for dm in orm["domains.DomainMember"].objects.all():
dm.domain = dm.site
dm.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', [], {'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'})
},
'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'})
},
'domains.domain': {
'Meta': {'object_name': 'Domain', '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', 'max_length': '60', 'null': 'True'})
},
'domains.domainmember': {
'Meta': {'unique_together': "(('site', 'user'),)", 'object_name': 'DomainMember', 'ordering': "['email']"},
'domain': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['domains.Domain']", 'null': 'True', 'related_name': "'+'"}),
'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', [], {'to': "orm['domains.Domain']", 'null': 'True', 'related_name': "'+'"}),
'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['users.User']", 'null': 'True', 'related_name': "'+'"})
},
'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', [], {'symmetrical': 'False', 'blank': 'True', 'to': "orm['auth.Group']", 'related_name': "'user_set'"}),
'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', [], {'max_length': '500', 'blank': 'True', 'null': 'True'}),
'token': ('django.db.models.fields.CharField', [], {'default': 'None', 'blank': 'True', 'max_length': '200', 'null': 'True'}),
'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'blank': 'True', 'to': "orm['auth.Permission']", 'related_name': "'user_set'"}),
'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
}
}
complete_apps = ['domains']
symmetrical = True

View File

@ -1,99 +0,0 @@
# -*- coding: utf-8 -*-
from south.utils import datetime_utils as 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 'DomainMember', fields ['site', 'user']
db.delete_unique('domains_domainmember', ['site_id', 'user_id'])
# Deleting field 'DomainMember.site'
db.delete_column('domains_domainmember', 'site_id')
# Adding unique constraint on 'DomainMember', fields ['domain', 'user']
db.create_unique('domains_domainmember', ['domain_id', 'user_id'])
def backwards(self, orm):
# Removing unique constraint on 'DomainMember', fields ['domain', 'user']
db.delete_unique('domains_domainmember', ['domain_id', 'user_id'])
# Adding field 'DomainMember.site'
db.add_column('domains_domainmember', 'site',
self.gf('django.db.models.fields.related.ForeignKey')(to=orm['domains.Domain'], related_name='+', null=True),
keep_default=False)
# Adding unique constraint on 'DomainMember', fields ['site', 'user']
db.create_unique('domains_domainmember', ['site_id', 'user_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', [], {'to': "orm['auth.Permission']", 'blank': 'True', 'symmetrical': 'False'})
},
'auth.permission': {
'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'},
'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'})
},
'contenttypes.contenttype': {
'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'db_table': "'django_content_type'", 'object_name': 'ContentType'},
'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'})
},
'domains.domain': {
'Meta': {'ordering': "('domain',)", 'object_name': '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'})
},
'domains.domainmember': {
'Meta': {'ordering': "['email']", 'unique_together': "(('domain', 'user'),)", 'object_name': 'DomainMember'},
'domain': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['domains.Domain']", 'related_name': "'+'", 'null': 'True'}),
'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'}),
'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['users.User']", 'related_name': "'+'", 'null': 'True'})
},
'users.user': {
'Meta': {'ordering': "['username']", 'object_name': 'User'},
'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', [], {'to': "orm['auth.Group']", 'blank': 'True', 'symmetrical': 'False', 'related_name': "'user_set'"}),
'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', [], {'to': "orm['auth.Permission']", 'blank': 'True', 'symmetrical': 'False', 'related_name': "'user_set'"}),
'username': ('django.db.models.fields.CharField', [], {'max_length': '30', 'unique': 'True'})
}
}
complete_apps = ['domains']

View File

@ -1,88 +0,0 @@
# -*- coding: utf-8 -*-
from south.utils import datetime_utils as datetime
from south.db import db
from south.v2 import SchemaMigration
from django.db import models
class Migration(SchemaMigration):
def forwards(self, orm):
# Adding field 'Domain.default_language'
db.add_column('domains_domain', 'default_language',
self.gf('django.db.models.fields.CharField')(default='', max_length=20, blank=True),
keep_default=False)
def backwards(self, orm):
# Deleting field 'Domain.default_language'
db.delete_column('domains_domain', 'default_language')
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', 'to': "orm['auth.Permission']", 'blank': 'True'})
},
'auth.permission': {
'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'},
'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'})
},
'contenttypes.contenttype': {
'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', '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'})
},
'domains.domain': {
'Meta': {'ordering': "('domain',)", 'object_name': 'Domain'},
'default_language': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '20', 'blank': 'True'}),
'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', [], {'default': 'None', 'max_length': '60', 'null': 'True'})
},
'domains.domainmember': {
'Meta': {'ordering': "['email']", 'unique_together': "(('domain', 'user'),)", 'object_name': 'DomainMember'},
'domain': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'+'", 'to': "orm['domains.Domain']", 'null': 'True'}),
'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'}),
'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'+'", 'to': "orm['users.User']", 'null': 'True'})
},
'users.user': {
'Meta': {'ordering': "['username']", 'object_name': 'User'},
'color': ('django.db.models.fields.CharField', [], {'default': "'#669933'", 'max_length': '9', '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', [], {'default': "''", 'max_length': '20', 'blank': 'True'}),
'default_timezone': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '20', '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'", 'to': "orm['auth.Group']", 'blank': 'True'}),
'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', [], {'default': "'all_owned_projects'", 'max_length': '32'}),
'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
'photo': ('django.db.models.fields.files.FileField', [], {'null': 'True', 'max_length': '500', 'blank': 'True'}),
'token': ('django.db.models.fields.CharField', [], {'default': 'None', 'null': 'True', 'max_length': '200', 'blank': 'True'}),
'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'user_set'", 'to': "orm['auth.Permission']", 'blank': 'True'}),
'username': ('django.db.models.fields.CharField', [], {'max_length': '30', 'unique': 'True'})
}
}
complete_apps = ['domains']

View File

@ -1,89 +0,0 @@
# -*- coding: utf-8 -*-
from south.utils import datetime_utils as datetime
from south.db import db
from south.v2 import SchemaMigration
from django.db import models
class Migration(SchemaMigration):
def forwards(self, orm):
# Adding field 'Domain.alias_of'
db.add_column('domains_domain', 'alias_of',
self.gf('django.db.models.fields.related.ForeignKey')(to=orm['domains.Domain'], default=None, blank=True, null=True, related_name='+'),
keep_default=False)
def backwards(self, orm):
# Deleting field 'Domain.alias_of'
db.delete_column('domains_domain', 'alias_of_id')
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', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
},
'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'})
},
'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'})
},
'domains.domain': {
'Meta': {'object_name': 'Domain', 'ordering': "('domain',)"},
'alias_of': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['domains.Domain']", 'default': 'None', 'blank': 'True', 'null': 'True', 'related_name': "'+'"}),
'default_language': ('django.db.models.fields.CharField', [], {'blank': 'True', 'default': "''", 'max_length': '20'}),
'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'})
},
'domains.domainmember': {
'Meta': {'object_name': 'DomainMember', 'unique_together': "(('domain', 'user'),)", 'ordering': "['email']"},
'domain': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['domains.Domain']", 'null': 'True', 'related_name': "'members'"}),
'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'}),
'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['users.User']", 'null': 'True', 'related_name': "'+'"})
},
'users.user': {
'Meta': {'object_name': 'User', 'ordering': "['username']"},
'color': ('django.db.models.fields.CharField', [], {'blank': 'True', 'default': "'#28261c'", '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', [], {'blank': 'True', 'default': "''", 'max_length': '20'}),
'default_timezone': ('django.db.models.fields.CharField', [], {'blank': 'True', 'default': "''", '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', [], {'to': "orm['auth.Group']", 'blank': 'True', 'symmetrical': 'False', 'related_name': "'user_set'"}),
'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', [], {'blank': 'True', 'default': 'None', 'null': 'True', 'max_length': '200'}),
'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'blank': 'True', 'symmetrical': 'False', 'related_name': "'user_set'"}),
'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
}
}
complete_apps = ['domains']

View File

@ -1,98 +0,0 @@
# Copyright (C) 2014 Andrey Antukh <niwi@niwi.be>
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import string
from django.db import models
from django.db.models.signals import pre_save, pre_delete
from django.dispatch import receiver
from django.utils.translation import ugettext_lazy as _
from django.core.exceptions import ValidationError
from .base import clear_domain_cache
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 Domain(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)
default_language = models.CharField(max_length=20, null=False, blank=True, default="",
verbose_name=_("default language"))
alias_of = models.ForeignKey("self", null=True, default=None, blank=True,
verbose_name=_("Mark as alias of"), related_name="+")
class Meta:
verbose_name = _('domain')
verbose_name_plural = _('domain')
ordering = ('domain',)
def __str__(self):
return self.domain
def user_is_owner(self, user):
return self.members.filter(user_id=user.id, is_owner=True).exists()
def user_is_staff(self, user):
return self.members.filter(user_id=user.id, is_staff=True).exists()
def user_is_normal_user(self, user):
return self.members.filter(user_id=user.id, is_owner=False, is_staff=False).exists()
class DomainMember(models.Model):
domain = models.ForeignKey("Domain", related_name="members", null=True)
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 = "Domain Member"
verbose_name_plural = "Domain Members"
unique_together = ("domain", "user")
def __str__(self):
return "DomainMember: {0}:{1}".format(self.domain, self.user)
pre_save.connect(clear_domain_cache, sender=Domain)
pre_delete.connect(clear_domain_cache, sender=Domain)
@receiver(pre_delete, sender=DomainMember, dispatch_uid="domain_member_pre_delete")
def domain_member_pre_delete(sender, instance, *args, **kwargs):
for domain_project in instance.domain.projects.all():
domain_project.memberships.filter(user=instance.user).delete()

View File

@ -1,43 +0,0 @@
# Copyright (C) 2014 Andrey Antukh <niwi@niwi.be>
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from rest_framework import permissions
from .models import DomainMember
from .base import get_active_domain
class DomainPermission(permissions.BasePermission):
safe_methods = ['HEAD', 'OPTIONS', 'GET']
def has_object_permission(self, request, view, obj):
if request.method in self.safe_methods:
return True
domain = get_active_domain()
return domain.user_is_owner(request.user)
class DomainMembersPermission(permissions.BasePermission):
safe_methods = ['HEAD', 'OPTIONS']
def has_permission(self, request, view):
if request.method in self.safe_methods:
return True
domain = get_active_domain()
if request.method in ["POST", "PUT", "PATCH", "GET"]:
return domain.user_is_owner(request.user)
return False

View File

@ -1,42 +0,0 @@
# Copyright (C) 2014 Andrey Antukh <niwi@niwi.be>
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from django.conf import settings
from rest_framework import serializers
from taiga.users.serializers import UserSerializer
from .models import Domain, DomainMember
class DomainSerializer(serializers.ModelSerializer):
projects = serializers.SerializerMethodField('get_projects')
default_project_template = serializers.SerializerMethodField('get_default_project_template')
class Meta:
model = Domain
fields = ('public_register', 'default_language', "projects", "default_project_template")
def get_projects(self, obj):
return map(lambda x: {"id": x.id, "name": x.name, "slug": x.slug, "owner": x.owner.id}, obj.projects.all().order_by('name'))
def get_default_project_template(self, obj):
return settings.DEFAULT_PROJECT_TEMPLATE
class DomainMemberSerializer(serializers.ModelSerializer):
user = UserSerializer()
class Meta:
model = DomainMember

View File

@ -1,65 +0,0 @@
# Copyright (C) 2014 Andrey Antukh <niwi@niwi.be>
# Copyright (C) 2014 Jesús Espino <jespinog@gmail.com>
# Copyright (C) 2014 David Barragán <bameda@dbarragan.com>
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
This module contains a domain logic for domains application.
"""
from django.db.models.loading import get_model
from django.db import transaction as tx
from django.db import IntegrityError
from taiga.base import exceptions as exc
def is_user_exists_on_domain(domain, user) -> bool:
"""
Checks if user is alredy exists on domain.
"""
return domain.members.filter(user=user).exists()
def is_public_register_enabled_for_domain(domain) -> bool:
"""
Checks if a specified domain have public register
activated.
The implementation is very simple but it encapsulates
request attribute access into more semantic function
call.
"""
return domain.public_register
@tx.atomic
def create_domain_member(domain, user):
"""
Given a domain and user, add user as member to
specified domain.
:returns: DomainMember
"""
domain_member_model = get_model("domains", "DomainMember")
try:
domain_member = domain_member_model(domain=domain, user=user,
email=user.email, is_owner=False,
is_staff=False)
domain_member.save()
except IntegrityError:
raise exc.IntegrityError("User is already member in a site")
return domain_member

View File

@ -1,118 +0,0 @@
# Copyright (C) 2014 Andrey Antukh <niwi@niwi.be>
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from django import test
from django.test.utils import override_settings
from django.core.exceptions import ImproperlyConfigured
from django.db.models import get_model
from django.http import HttpResponse
from . import base
from .models import Domain
from .middleware import DomainsMiddleware
class DomainCoreTests(test.TestCase):
fixtures = ["initial_domains.json"]
def setUp(self):
base.clear_domain_cache()
@override_settings(DOMAIN_ID=1)
def test_get_default_domain(self):
default_domain = base.get_default_domain()
self.assertEqual(default_domain.domain, "localhost")
@override_settings(DOMAIN_ID=2)
def test_get_wrong_default_domain(self):
with self.assertRaises(ImproperlyConfigured):
default_domain = base.get_default_domain()
def test_get_domain_by_name(self):
domain = base.get_domain_for_domain_name("localhost")
self.assertEqual(domain.id, 1)
self.assertEqual(domain.domain, "localhost")
def test_get_domain_by_name_aliased(self):
main_domain = base.get_default_domain()
aliased_domain = Domain.objects.create(domain="beta.localhost", scheme="http",
alias_of=main_domain)
resolved_domain = base.get_domain_for_domain_name("beta.localhost", follow_alias=False)
self.assertEqual(resolved_domain.domain, "beta.localhost")
resolved_domain = base.get_domain_for_domain_name("beta.localhost", follow_alias=True)
self.assertEqual(resolved_domain.domain, "localhost")
def test_lru_cache_for_get_default_domain(self):
with self.assertNumQueries(1):
base.get_default_domain()
base.get_default_domain()
def test_lru_cache_for_get_domain_for_domain_name(self):
with self.assertNumQueries(2):
base.get_domain_for_domain_name("localhost", follow_alias=True)
base.get_domain_for_domain_name("localhost", follow_alias=True)
base.get_domain_for_domain_name("localhost", follow_alias=False)
base.get_domain_for_domain_name("localhost", follow_alias=False)
def test_activate_deactivate_domain(self):
main_domain = base.get_default_domain()
aliased_domain = Domain.objects.create(domain="beta.localhost", scheme="http",
alias_of=main_domain)
self.assertEqual(base.get_active_domain(), main_domain)
base.activate(aliased_domain)
self.assertEqual(base.get_active_domain(), aliased_domain)
base.deactivate()
self.assertEqual(base.get_active_domain(), main_domain)
from django.test.client import RequestFactory
class DomainMiddlewareTests(test.TestCase):
fixtures = ["initial_domains.json"]
def setUp(self):
self.main_domain = base.get_default_domain()
self.aliased_domain = Domain.objects.create(domain="beta.localhost", scheme="http",
alias_of=self.main_domain)
self.factory = RequestFactory()
def test_process_request(self):
request = self.factory.get("/", HTTP_X_HOST="beta.localhost")
middleware = DomainsMiddleware()
ret = middleware.process_request(request)
self.assertEqual(request.domain, self.main_domain)
self.assertEqual(ret, None)
def test_process_request_with_wrong_domain(self):
request = self.factory.get("/", HTTP_X_HOST="beta2.localhost")
middleware = DomainsMiddleware()
ret = middleware.process_request(request)
self.assertFalse(hasattr(request, "domain"))
self.assertNotEqual(ret, None)
self.assertIsInstance(ret, HttpResponse)
def test_process_request_without_host_header(self):
request = self.factory.get("/")
middleware = DomainsMiddleware()
ret = middleware.process_request(request)
self.assertEqual(request.domain, self.main_domain)
self.assertEqual(ret, None)

View File

@ -13,11 +13,12 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from django.conf import settings
from django_jinja import library
from django_sites import get_by_id as get_site_by_id
from taiga import domains
URLS = {
urls = {
"home": "/",
"backlog": "/#/project/{0}/backlog/",
"taskboard": "/#/project/{0}/taskboard/{1}",
@ -31,14 +32,11 @@ URLS = {
}
lib = library.Library()
@lib.global_function(name="resolve_front_url")
@library.global_function(name="resolve_front_url")
def resolve(type, *args):
domain = domains.get_active_domain()
site = get_site_by_id("front")
url_tmpl = "{scheme}//{domain}{url}"
scheme = domain.scheme and "{0}:".format(domain.scheme) or ""
url = URLS[type].format(*args)
return url_tmpl.format(scheme=scheme, domain=domain.domain, url=url)
scheme = site.scheme and "{0}:".format(site.scheme) or ""
url = urlsp[type].format(*args)
return url_tmpl.format(scheme=scheme, domain=site.domain, url=url)

View File

@ -34,7 +34,7 @@ class MembershipInline(admin.TabularInline):
class ProjectAdmin(admin.ModelAdmin):
list_display = ["name", "owner", "created_date", "total_milestones",
"total_story_points", "domain"]
"total_story_points"]
list_display_links = list_display
inlines = [RoleInline, MembershipInline, MilestoneInline]

View File

@ -28,7 +28,6 @@ from rest_framework import status
from djmail.template_mail import MagicMailBuilder
from taiga.domains import get_active_domain
from taiga.base import filters
from taiga.base import exceptions as exc
from taiga.base.decorators import list_route, detail_route
@ -50,8 +49,7 @@ class ProjectAdminViewSet(ModelCrudViewSet):
permission_classes = (IsAuthenticated, permissions.ProjectAdminPermission)
def get_queryset(self):
domain = get_active_domain()
return domain.projects.all()
return models.Project.objects.all()
def pre_save(self, obj):
if not obj.id:
@ -61,13 +59,6 @@ class ProjectAdminViewSet(ModelCrudViewSet):
if not obj.id:
obj.template = self.request.QUERY_PARAMS.get('template', None)
# FIXME
# Assign domain only if it current
# value is None
if not obj.domain:
obj.domain = self.request.domain
super().pre_save(obj)
@ -100,20 +91,13 @@ class ProjectViewSet(ModelCrudViewSet):
def get_queryset(self):
qs = super().get_queryset()
qs = qs.filter(Q(owner=self.request.user) |
Q(members=self.request.user)).filter(domain=get_active_domain())
Q(members=self.request.user))
return qs.distinct()
def pre_save(self, obj):
if not obj.id:
obj.owner = self.request.user
# FIXME
# Assign domain only if it current
# value is None
if not obj.domain:
obj.domain = self.request.domain
super().pre_save(obj)
@ -310,10 +294,8 @@ class ProjectTemplateViewSet(ModelCrudViewSet):
template_slug = slugify_uniquely(template_name, models.ProjectTemplate)
domain = get_active_domain()
try:
project = models.Project.objects.get(domain=domain, pk=project_id)
project = models.Project.objects.get(pk=project_id)
except models.Project.DoesNotExist:
raise ParseError("Not valid project_id")
@ -321,12 +303,11 @@ class ProjectTemplateViewSet(ModelCrudViewSet):
name=template_name,
slug=template_slug,
description=template_description,
domain=domain,
)
template.load_data_from_project(project)
template.save()
return Response(self.serializer_class(template).data, status=201)
def get_queryset(self):
domain = get_active_domain()
return models.ProjectTemplate.objects.filter(Q(domain=domain) | Q(domain__isnull=True))
return models.ProjectTemplate.objects.all()

View File

@ -23,8 +23,7 @@
"us_statuses": "[{\"order\": 1, \"name\": \"Open\", \"color\": \"#669933\", \"is_closed\": false, \"wip_limit\": null}, {\"order\": 2, \"name\": \"Closed\", \"color\": \"#999999\", \"is_closed\": true, \"wip_limit\": null}]",
"videoconferences_salt": null,
"priorities": "[{\"order\": 1, \"name\": \"Low\", \"color\": \"#666666\"}, {\"order\": 3, \"name\": \"Normal\", \"color\": \"#669933\"}, {\"order\": 5, \"name\": \"High\", \"color\": \"#CC0000\"}]",
"severities": "[{\"order\": 1, \"name\": \"Wishlist\", \"color\": \"#666666\"}, {\"order\": 2, \"name\": \"Minor\", \"color\": \"#669933\"}, {\"order\": 3, \"name\": \"Normal\", \"color\": \"#0000FF\"}, {\"order\": 4, \"name\": \"Important\", \"color\": \"#FFA500\"}, {\"order\": 5, \"name\": \"Critical\", \"color\": \"#CC0000\"}]",
"domain": null
"severities": "[{\"order\": 1, \"name\": \"Wishlist\", \"color\": \"#666666\"}, {\"order\": 2, \"name\": \"Minor\", \"color\": \"#669933\"}, {\"order\": 3, \"name\": \"Normal\", \"color\": \"#0000FF\"}, {\"order\": 4, \"name\": \"Important\", \"color\": \"#FFA500\"}, {\"order\": 5, \"name\": \"Critical\", \"color\": \"#CC0000\"}]"
}
},
{
@ -51,8 +50,7 @@
"us_statuses": "[{\"order\": 1, \"name\": \"To do\", \"color\": \"#999999\", \"is_closed\": false, \"wip_limit\": null}, {\"order\": 2, \"name\": \"Doing\", \"color\": \"#ff9900\", \"is_closed\": false, \"wip_limit\": null}, {\"order\": 3, \"name\": \"Done\", \"color\": \"#ffcc00\", \"is_closed\": true, \"wip_limit\": null}]",
"videoconferences_salt": null,
"priorities": "[{\"order\": 1, \"name\": \"Low\", \"color\": \"#666666\"}, {\"order\": 3, \"name\": \"Normal\", \"color\": \"#669933\"}, {\"order\": 5, \"name\": \"High\", \"color\": \"#CC0000\"}]",
"severities": "[{\"order\": 1, \"name\": \"Wishlist\", \"color\": \"#666666\"}, {\"order\": 2, \"name\": \"Minor\", \"color\": \"#669933\"}, {\"order\": 3, \"name\": \"Normal\", \"color\": \"#0000FF\"}, {\"order\": 4, \"name\": \"Important\", \"color\": \"#FFA500\"}, {\"order\": 5, \"name\": \"Critical\", \"color\": \"#CC0000\"}]",
"domain": null
"severities": "[{\"order\": 1, \"name\": \"Wishlist\", \"color\": \"#666666\"}, {\"order\": 2, \"name\": \"Minor\", \"color\": \"#669933\"}, {\"order\": 3, \"name\": \"Normal\", \"color\": \"#0000FF\"}, {\"order\": 4, \"name\": \"Important\", \"color\": \"#FFA500\"}, {\"order\": 5, \"name\": \"Critical\", \"color\": \"#CC0000\"}]"
}
}
]

View File

@ -281,7 +281,6 @@ class Command(BaseCommand):
name='Project Example {0}'.format(counter),
description='Project example {0} description'.format(counter),
owner=random.choice(self.users),
domain_id=1,
public=True,
total_story_points=self.sd.int(600, 3000),
total_milestones=self.sd.int(5,10))

View File

@ -0,0 +1,224 @@
# -*- coding: utf-8 -*-
from south.utils import datetime_utils as 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 'ProjectTemplate', fields ['slug', 'domain']
db.delete_unique('projects_projecttemplate', ['slug', 'domain_id'])
# Deleting field 'ProjectTemplate.domain'
db.delete_column('projects_projecttemplate', 'domain_id')
# Adding unique constraint on 'ProjectTemplate', fields ['slug']
db.create_unique('projects_projecttemplate', ['slug'])
# Deleting field 'Project.domain'
db.delete_column('projects_project', 'domain_id')
def backwards(self, orm):
# Removing unique constraint on 'ProjectTemplate', fields ['slug']
db.delete_unique('projects_projecttemplate', ['slug'])
# Adding field 'ProjectTemplate.domain'
db.add_column('projects_projecttemplate', 'domain',
self.gf('django.db.models.fields.related.ForeignKey')(default=None, related_name='templates', to=orm['domains.Domain'], blank=True, null=True),
keep_default=False)
# Adding unique constraint on 'ProjectTemplate', fields ['slug', 'domain']
db.create_unique('projects_projecttemplate', ['slug', 'domain_id'])
# Adding field 'Project.domain'
db.add_column('projects_project', 'domain',
self.gf('django.db.models.fields.related.ForeignKey')(default=None, related_name='projects', to=orm['domains.Domain'], blank=True, null=True),
keep_default=False)
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', [], {'to': "orm['auth.Permission']", 'blank': 'True', 'symmetrical': 'False'})
},
'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'})
},
'contenttypes.contenttype': {
'Meta': {'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', '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.issuestatus': {
'Meta': {'unique_together': "(('project', 'name'),)", 'object_name': 'IssueStatus', 'ordering': "['project', 'order', 'name']"},
'color': ('django.db.models.fields.CharField', [], {'default': "'#999999'", 'max_length': '20'}),
'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', [], {'to': "orm['projects.Project']", 'related_name': "'issue_statuses'"})
},
'projects.issuetype': {
'Meta': {'unique_together': "(('project', 'name'),)", 'object_name': 'IssueType', 'ordering': "['project', 'order', 'name']"},
'color': ('django.db.models.fields.CharField', [], {'default': "'#999999'", 'max_length': '20'}),
'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', [], {'to': "orm['projects.Project']", 'related_name': "'issue_types'"})
},
'projects.membership': {
'Meta': {'unique_together': "(('user', 'project'),)", 'object_name': 'Membership', 'ordering': "['project', 'role']"},
'created_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now', 'auto_now_add': 'True', 'blank': 'True'}),
'email': ('django.db.models.fields.EmailField', [], {'default': 'None', 'max_length': '255', 'null': 'True', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'project': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['projects.Project']", 'related_name': "'memberships'"}),
'role': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['users.Role']", 'related_name': "'memberships'"}),
'token': ('django.db.models.fields.CharField', [], {'default': 'None', 'max_length': '60', 'null': 'True', 'blank': 'True'}),
'user': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'null': 'True', 'to': "orm['users.User']", 'related_name': "'memberships'", 'blank': 'True'})
},
'projects.points': {
'Meta': {'unique_together': "(('project', 'name'),)", 'object_name': 'Points', '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', [], {'to': "orm['projects.Project']", 'related_name': "'points'"}),
'value': ('django.db.models.fields.FloatField', [], {'default': 'None', 'null': 'True', 'blank': 'True'})
},
'projects.priority': {
'Meta': {'unique_together': "(('project', 'name'),)", 'object_name': 'Priority', 'ordering': "['project', 'order', 'name']"},
'color': ('django.db.models.fields.CharField', [], {'default': "'#999999'", 'max_length': '20'}),
'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', [], {'to': "orm['projects.Project']", 'related_name': "'priorities'"})
},
'projects.project': {
'Meta': {'object_name': 'Project', 'ordering': "['name']"},
'created_date': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'creation_template': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'null': 'True', 'to': "orm['projects.ProjectTemplate']", 'related_name': "'projects'", 'blank': 'True'}),
'default_issue_status': ('django.db.models.fields.related.OneToOneField', [], {'unique': 'True', 'related_name': "'+'", 'to': "orm['projects.IssueStatus']", 'null': 'True', 'on_delete': 'models.SET_NULL', 'blank': 'True'}),
'default_issue_type': ('django.db.models.fields.related.OneToOneField', [], {'unique': 'True', 'related_name': "'+'", 'to': "orm['projects.IssueType']", 'null': 'True', 'on_delete': 'models.SET_NULL', 'blank': 'True'}),
'default_points': ('django.db.models.fields.related.OneToOneField', [], {'unique': 'True', 'related_name': "'+'", 'to': "orm['projects.Points']", 'null': 'True', 'on_delete': 'models.SET_NULL', 'blank': 'True'}),
'default_priority': ('django.db.models.fields.related.OneToOneField', [], {'unique': 'True', 'related_name': "'+'", 'to': "orm['projects.Priority']", 'null': 'True', 'on_delete': 'models.SET_NULL', 'blank': 'True'}),
'default_severity': ('django.db.models.fields.related.OneToOneField', [], {'unique': 'True', 'related_name': "'+'", 'to': "orm['projects.Severity']", 'null': 'True', 'on_delete': 'models.SET_NULL', 'blank': 'True'}),
'default_task_status': ('django.db.models.fields.related.OneToOneField', [], {'unique': 'True', 'related_name': "'+'", 'to': "orm['projects.TaskStatus']", 'null': 'True', 'on_delete': 'models.SET_NULL', 'blank': 'True'}),
'default_us_status': ('django.db.models.fields.related.OneToOneField', [], {'unique': 'True', 'related_name': "'+'", 'to': "orm['projects.UserStoryStatus']", 'null': 'True', 'on_delete': 'models.SET_NULL', 'blank': 'True'}),
'description': ('django.db.models.fields.TextField', [], {}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'is_backlog_activated': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'is_issues_activated': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'is_kanban_activated': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'is_wiki_activated': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'members': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['users.User']", 'related_name': "'projects'", 'through': "orm['projects.Membership']", 'symmetrical': 'False'}),
'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', [], {'to': "orm['users.User']", 'related_name': "'owned_projects'"}),
'public': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'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', [], {'default': '0', 'null': 'True', 'blank': 'True'}),
'total_story_points': ('django.db.models.fields.FloatField', [], {'default': 'None', 'null': 'True'}),
'videoconferences': ('django.db.models.fields.CharField', [], {'max_length': '250', 'null': 'True', 'blank': 'True'}),
'videoconferences_salt': ('django.db.models.fields.CharField', [], {'max_length': '250', 'null': 'True', 'blank': 'True'})
},
'projects.projecttemplate': {
'Meta': {'object_name': 'ProjectTemplate', 'ordering': "['name']"},
'created_date': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'default_options': ('django_pgjson.fields.JsonField', [], {}),
'default_owner_role': ('django.db.models.fields.CharField', [], {'max_length': '50'}),
'description': ('django.db.models.fields.TextField', [], {}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'is_backlog_activated': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'is_issues_activated': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'is_kanban_activated': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'is_wiki_activated': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'issue_statuses': ('django_pgjson.fields.JsonField', [], {}),
'issue_types': ('django_pgjson.fields.JsonField', [], {}),
'modified_date': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '250'}),
'points': ('django_pgjson.fields.JsonField', [], {}),
'priorities': ('django_pgjson.fields.JsonField', [], {}),
'roles': ('django_pgjson.fields.JsonField', [], {}),
'severities': ('django_pgjson.fields.JsonField', [], {}),
'slug': ('django.db.models.fields.SlugField', [], {'max_length': '250', 'unique': 'True', 'blank': 'True'}),
'task_statuses': ('django_pgjson.fields.JsonField', [], {}),
'us_statuses': ('django_pgjson.fields.JsonField', [], {}),
'videoconferences': ('django.db.models.fields.CharField', [], {'max_length': '250', 'null': 'True', 'blank': 'True'}),
'videoconferences_salt': ('django.db.models.fields.CharField', [], {'max_length': '250', 'null': 'True', 'blank': 'True'})
},
'projects.severity': {
'Meta': {'unique_together': "(('project', 'name'),)", 'object_name': 'Severity', 'ordering': "['project', 'order', 'name']"},
'color': ('django.db.models.fields.CharField', [], {'default': "'#999999'", 'max_length': '20'}),
'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', [], {'to': "orm['projects.Project']", 'related_name': "'severities'"})
},
'projects.taskstatus': {
'Meta': {'unique_together': "(('project', 'name'),)", 'object_name': 'TaskStatus', 'ordering': "['project', 'order', 'name']"},
'color': ('django.db.models.fields.CharField', [], {'default': "'#999999'", 'max_length': '20'}),
'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', [], {'to': "orm['projects.Project']", 'related_name': "'task_statuses'"})
},
'projects.userstorystatus': {
'Meta': {'unique_together': "(('project', 'name'),)", 'object_name': 'UserStoryStatus', 'ordering': "['project', 'order', 'name']"},
'color': ('django.db.models.fields.CharField', [], {'default': "'#999999'", 'max_length': '20'}),
'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', [], {'to': "orm['projects.Project']", 'related_name': "'us_statuses'"}),
'wip_limit': ('django.db.models.fields.IntegerField', [], {'default': 'None', 'null': 'True', 'blank': 'True'})
},
'users.role': {
'Meta': {'unique_together': "(('slug', 'project'),)", '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', [], {'to': "orm['auth.Permission']", 'related_name': "'roles'", 'symmetrical': 'False'}),
'project': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['projects.Project']", 'related_name': "'roles'"}),
'slug': ('django.db.models.fields.SlugField', [], {'max_length': '250', 'blank': 'True'})
},
'users.user': {
'Meta': {'object_name': 'User', 'ordering': "['username']"},
'color': ('django.db.models.fields.CharField', [], {'default': "'#9d3a9a'", 'max_length': '9', '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', [], {'default': "''", 'max_length': '20', 'blank': 'True'}),
'default_timezone': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '20', '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', [], {'to': "orm['auth.Group']", 'related_name': "'user_set'", 'blank': 'True', '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', [], {'max_length': '30', 'blank': 'True'}),
'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', [], {'max_length': '500', 'null': 'True', 'blank': 'True'}),
'token': ('django.db.models.fields.CharField', [], {'default': 'None', 'max_length': '200', 'null': 'True', 'blank': 'True'}),
'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'related_name': "'user_set'", 'blank': 'True', 'symmetrical': 'False'}),
'username': ('django.db.models.fields.CharField', [], {'max_length': '30', 'unique': 'True'})
}
}
complete_apps = ['projects']

View File

@ -31,8 +31,6 @@ from picklefield.fields import PickledObjectField
from django_pgjson.fields import JsonField
from taiga.users.models import Role
from taiga.domains.models import DomainMember
from taiga.domains import get_active_domain
from taiga.projects.userstories.models import UserStory
from taiga.base.utils.slug import slugify_uniquely
from taiga.base.utils.dicts import dict_sum
@ -154,9 +152,6 @@ class Project(ProjectDefaults, models.Model):
blank=True, default=None,
verbose_name=_("creation template"))
domain = models.ForeignKey("domains.Domain", related_name="projects", null=True, blank=True,
default=None, verbose_name=_("domain"))
class Meta:
verbose_name = "project"
verbose_name_plural = "projects"
@ -441,15 +436,13 @@ class ProjectTemplate(models.Model):
name = models.CharField(max_length=250, null=False, blank=False,
verbose_name=_("name"))
slug = models.SlugField(max_length=250, null=False, blank=True,
verbose_name=_("slug"))
verbose_name=_("slug"), unique=True)
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"))
domain = models.ForeignKey("domains.Domain", related_name="templates", null=True, blank=True,
default=None, verbose_name=_("domain"))
default_owner_role = models.CharField(max_length=50, null=False,
blank=False,
verbose_name=_("default owner's role"))
@ -481,7 +474,6 @@ class ProjectTemplate(models.Model):
class Meta:
verbose_name = "project template"
verbose_name_plural = "project templates"
unique_together = ["slug", "domain"]
ordering = ["name"]
def __str__(self):
@ -704,21 +696,6 @@ class ProjectTemplate(models.Model):
return project
# On membership object is created/changed, update
# role-points relation.
@receiver(signals.post_save, sender=Membership, dispatch_uid='membership_post_save')
def membership_post_save(sender, instance, created, **kwargs):
instance.project.update_role_points()
exists_user_on_domain = instance.project.domain.members.filter(user=instance.user).exists()
if instance.user and instance.project.domain and not exists_user_on_domain:
DomainMember.objects.create(domain=instance.project.domain,
user=instance.user,
email=instance.email,
is_owner=False,
is_staff=False)
# On membership object is deleted, update role-points relation.
@receiver(signals.pre_delete, sender=Membership, dispatch_uid='membership_pre_delete')
def membership_post_delete(sender, instance, using, **kwargs):
@ -748,9 +725,7 @@ def project_post_save(sender, instance, created, **kwargs):
return
template_slug = getattr(instance, "template", settings.DEFAULT_PROJECT_TEMPLATE)
template = ProjectTemplate.objects.filter(slug=template_slug).get(
models.Q(domain__isnull=True) | models.Q(domain=get_active_domain())
)
template = ProjectTemplate.objects.get(slug=template_slug)
template.apply_to_project(instance)
instance.save()

View File

@ -15,7 +15,6 @@
from taiga.base.permissions import BasePermission
from taiga.domains import get_active_domain
class ProjectPermission(BasePermission):
@ -27,28 +26,16 @@ class ProjectPermission(BasePermission):
safe_methods = ["HEAD", "OPTIONS"]
path_to_project = []
class ProjectAdminPermission(BasePermission):
def has_permission(self, request, view):
if request.method in self.safe_methods:
return True
domain = get_active_domain()
if request.method in ["POST", "PUT", "GET", "PATCH"]:
return domain.user_is_staff(request.user) or domain.user_is_owner(request.user)
elif request.method == "DELETE":
return domain.user_is_owner(request.user)
return super().has_permission(request, view)
def has_object_permission(self, request, view, obj):
if request.method in self.safe_methods:
return True
domain = get_active_domain()
if request.method in ["POST", "PUT", "GET", "PATCH"]:
return domain.user_is_staff(request.user) or domain.user_is_owner(request.user)
elif request.method == "DELETE":
return domain.user_is_owner(request.user) or (
domain.user_is_staff(request.user) and obj.user == request.user)
return super().has_object_permission(request, view, obj)
@ -151,16 +138,5 @@ class RolesPermission(BasePermission):
# Project Templates
class ProjectTemplatePermission(BasePermission):
def has_permission(self, request, view):
domain = get_active_domain()
return domain.user_is_owner(request.user)
def has_object_permission(self, request, view, obj):
current_domain = get_active_domain()
if obj.domain:
return obj.domain == current_domain and current_domain.user_is_owner(request.user)
else:
if request.method == "GET":
return current_domain.user_is_owner(request.user)
else:
False
# TODO: should be improved in permissions refactor
pass

View File

@ -18,7 +18,7 @@ from os import path
from rest_framework import serializers
from django.utils.translation import ugettext_lazy as _
from taiga.base.serializers import PickleField, JsonField, AutoDomainField
from taiga.base.serializers import PickleField, JsonField
from taiga.users.models import Role
from . import models
@ -87,7 +87,7 @@ class ProjectSerializer(serializers.ModelSerializer):
class Meta:
model = models.Project
read_only_fields = ("created_date", "modified_date", "owner", "domain")
read_only_fields = ("created_date", "modified_date", "owner")
exclude = ("last_us_ref", "last_task_ref", "last_issue_ref")
@ -138,8 +138,6 @@ class RoleSerializer(serializers.ModelSerializer):
class ProjectTemplateSerializer(serializers.ModelSerializer):
domain = AutoDomainField(required=False, label=_("Domain"))
default_options = JsonField(required=False, label=_("Default options"))
us_statuses = JsonField(required=False, label=_("User story's statuses"))
points = JsonField(required=False, label=_("Points"))

View File

@ -42,13 +42,7 @@ router.register(r"resolver", ResolverViewSet, base_name="resolver")
router.register(r"search", SearchViewSet, base_name="search")
# Domains
from taiga.domains.api import DomainViewSet
from taiga.domains.api import DomainMembersViewSet
from taiga.projects.api import ProjectAdminViewSet
router.register(r"sites", DomainViewSet, base_name="sites")
router.register(r"site-members", DomainMembersViewSet, base_name="site-members")
router.register(r"site-projects", ProjectAdminViewSet, base_name="site-projects")

View File

@ -63,14 +63,12 @@ class PermissionsViewSet(ModelListViewSet):
"add_permission", "change_permission", "delete_permission",
"add_contenttype", "change_contenttype", "delete_contenttype",
"add_message", "change_message", "delete_message",
"add_domain", "change_domain", "delete_domain",
"add_session", "change_session", "delete_session",
"add_migrationhistory", "change_migrationhistory", "delete_migrationhistory",
"add_version", "change_version", "delete_version",
"add_revision", "change_revision", "delete_revision",
"add_user", "delete_user",
"add_project",
"add_domainmember", "change_domainmember", "delete_domainmember",
]
def get_queryset(self):

View File

@ -23,26 +23,5 @@
"email": "niwi@niwi.be",
"date_joined": "2013-04-01T13:48:21.711Z"
}
},
{
"model": "domains.domain",
"pk": 1,
"fields": {
"domain": "localhost",
"name": "localhost",
"scheme": "http",
"public_register": false
}
},
{
"model": "domains.domainmember",
"pk": 1,
"fields": {
"is_staff": true,
"is_owner": true,
"user": 1,
"domain": 1,
"email": "niwi@niwi.be"
}
}
]

View File

@ -3,7 +3,6 @@ import uuid
import factory
from django.conf import settings
import taiga.domains.models
import taiga.projects.models
import taiga.projects.userstories.models
import taiga.projects.issues.models
@ -12,18 +11,9 @@ import taiga.users.models
import taiga.userstorage.models
class DomainFactory(factory.DjangoModelFactory):
FACTORY_FOR = taiga.domains.models.Domain
FACTORY_DJANGO_GET_OR_CREATE = ("domain",)
name = "Default domain"
domain = "default"
scheme = None
public_register = False
class ProjectTemplateFactory(factory.DjangoModelFactory):
FACTORY_FOR = taiga.projects.models.ProjectTemplate
FACTORY_DJANGO_GET_OR_CREATE = ("slug", )
name = "Template name"
slug = settings.DEFAULT_PROJECT_TEMPLATE
@ -45,7 +35,6 @@ class ProjectFactory(factory.DjangoModelFactory):
slug = factory.Sequence(lambda n: "project-{}-slug".format(n))
description = "Project description"
owner = factory.SubFactory("tests.factories.UserFactory")
domain = factory.SubFactory("tests.factories.DomainFactory")
creation_template = factory.SubFactory("tests.factories.ProjectTemplateFactory")
@ -67,7 +56,7 @@ class UserFactory(factory.DjangoModelFactory):
class MembershipFactory(factory.DjangoModelFactory):
FACTORY_FOR = taiga.projects.models.Membership
token = factory.LazyAttribute(lambda obj: uuid.uuid1())
token = factory.LazyAttribute(lambda obj: str(uuid.uuid1()))
project = factory.SubFactory("tests.factories.ProjectFactory")
role = factory.SubFactory("tests.factories.RoleFactory")
user = factory.SubFactory("tests.factories.UserFactory")

View File

@ -1,50 +1,42 @@
from unittest import mock
import pytest
from django.core.urlresolvers import reverse
from .. import factories
pytestmark = pytest.mark.django_db
def setup_module(module):
module.patcher = mock.patch("taiga.domains.base.get_default_domain",
mock.Mock(return_value=factories.DomainFactory()))
module.patcher.start()
def teardown_module(module):
module.patcher.stop()
@pytest.fixture
def register_form():
return {"username": "username",
"password": "password",
"first_name": "fname",
"last_name": "lname",
"email": "user@email.com",
"type": "public"}
class TestPublicRegistration:
@classmethod
def setup_class(cls):
cls.form = {"username": "username", "password": "password", "first_name": "fname",
"last_name": "lname", "email": "user@email.com", "type": "public"}
def test_respond_201_if_domain_allows_public_registration(self, client):
domain = factories.DomainFactory(public_register=True)
response = client.post(reverse("auth-register"), self.form, HTTP_X_HOST=domain.domain)
assert response.status_code == 201
def test_respond_400_if_domain_does_not_allow_public_registration(self, client):
domain = factories.DomainFactory(public_register=False)
response = client.post(reverse("auth-register"), self.form, HTTP_X_HOST=domain.domain)
assert response.status_code == 400
def test_respond_201_if_domain_allows_public_registration(client, register_form):
response = client.post(reverse("auth-register"), register_form)
assert response.status_code == 201
@pytest.mark.xfail
class TestPrivateRegistration:
@classmethod
def setup_class(cls):
cls.form = {"username": "username", "password": "password", "first_name": "fname",
"last_name": "lname", "email": "user@email.com", "type": "private",
"existing": "1"}
def test_respond_400_if_domain_does_not_allow_public_registration(client, register_form):
response = client.post(reverse("auth-register"), register_form)
assert response.status_code == 400
def test_respond_201_if_domain_allows_public_registration(self, client):
domain = factories.DomainFactory(public_register=True)
membership = factories.MembershipFactory()
headers = {"HTTP_X_HOST": domain.domain}
response = client.post(reverse("auth-register"), self.form, **headers)
assert response.status_code == 201
def test_respond_201_if_domain_allows_public_registration(client, register_form):
user = factories.UserFactory()
membership = factories.MembershipFactory(user=user)
register_form.update({
"type": "private",
"existing": "1",
"token": membership.token,
"username": user.username,
"email": user.email,
"password": user.username,
})
response = client.post(reverse("auth-register"), register_form)
assert response.status_code == 201

View File

@ -11,11 +11,13 @@ def seq():
from taiga.projects.references import sequences as seq
return seq
@pytest.fixture
def refmodels():
from taiga.projects.references import models
return models
@pytest.mark.django_db
def test_sequences(seq):
seqname = "foo"
@ -47,14 +49,10 @@ def test_sequences(seq):
@pytest.mark.django_db
def test_unique_reference_per_project(seq, refmodels):
# management.call_command("loaddata", "initial_project_templates")
domain = factories.DomainFactory(public_register=True)
settings.DOMAIN_ID = domain.id
project = factories.ProjectFactory.create()
seqname = refmodels.make_sequence_name(project)
assert seqname == "references_project1"
assert seqname == "references_project{0}".format(project.id)
assert seq.exists(seqname)
assert refmodels.make_unique_reference_id(project, create=True) == 1

View File

@ -8,211 +8,167 @@ from .. import factories
pytestmark = pytest.mark.django_db
class TestListStorageEntries(object):
def _load_initial_data(self):
self.user1 = factories.UserFactory()
self.user2 = factories.UserFactory()
self.storage11 = factories.StorageEntryFactory(owner=self.user1)
self.storage12 = factories.StorageEntryFactory(owner=self.user1)
self.storage13 = factories.StorageEntryFactory(owner=self.user1)
self.storage21 = factories.StorageEntryFactory(owner=self.user2)
def test_list_userstories(client):
user1 = factories.UserFactory()
user2 = factories.UserFactory()
storage11 = factories.StorageEntryFactory(owner=user1)
storage12 = factories.StorageEntryFactory(owner=user1)
storage13 = factories.StorageEntryFactory(owner=user1)
storage21 = factories.StorageEntryFactory(owner=user2)
def test_list_by_anonymous_user(self, client):
self._load_initial_data()
response = client.get(reverse("user-storage-list"))
assert response.status_code == 401
# List by anonumous user
response = client.get(reverse("user-storage-list"))
assert response.status_code == 401
def test_list_only_user1_entriees(self, client):
self._load_initial_data()
response = client.login(username=self.user1.username, password=self.user1.username)
response = client.get(reverse("user-storage-list"))
assert response.status_code == 200
entries = response.data
assert len(entries) == 3
response = client.logout()
# List own entries
client.login(username=user1.username, password=user1.username)
response = client.get(reverse("user-storage-list"))
assert response.status_code == 200
assert len(response.data) == 3
def test_list_only_user2_entriees(self, client):
self._load_initial_data()
response = client.login(username=self.user2.username, password=self.user2.username)
response = client.get(reverse("user-storage-list"))
assert response.status_code == 200
entries = response.data
assert len(entries) == 1
response = client.logout()
client.login(username=user2.username, password=user2.username)
response = client.get(reverse("user-storage-list"))
assert response.status_code == 200
assert len(response.data) == 1
def test_list_only_user1_entriees_filter_by_keys(self, client):
self._load_initial_data()
response = client.login(username=self.user1.username, password=self.user1.username)
keys = "{},{}".format(self.storage11.key, self.storage13.key)
response = client.get("{}?keys={}".format(reverse("user-storage-list"), keys))
assert response.status_code == 200
entries = response.data
assert len(entries) == 2
response = client.logout()
# Filter results by key
client.login(username=user1.username, password=user1.username)
keys = ",".join([storage11.key, storage13.key])
url = "{}?keys={}".format(reverse("user-storage-list"), keys)
response = client.get(url)
assert response.status_code == 200
assert len(response.data) == 2
client.logout()
class TestViewStorageEntries(object):
def _load_initial_data(self):
self.user1 = factories.UserFactory()
self.user2 = factories.UserFactory()
self.storage11 = factories.StorageEntryFactory(owner=self.user1)
def test_view_storage_entries(client):
user1 = factories.UserFactory()
user2 = factories.UserFactory()
storage11 = factories.StorageEntryFactory(owner=user1)
def test_view_an_entry_by_anonymous_user(self, client):
self._load_initial_data()
response = client.get(reverse("user-storage-detail", args=[self.storage11.key]))
assert response.status_code == 401
# Get by anonymous user
response = client.get(reverse("user-storage-detail", args=[storage11.key]))
assert response.status_code == 401
def test_view_an_entry(self, client):
self._load_initial_data()
response = client.login(username=self.user1.username, password=self.user1.username)
response = client.get(reverse("user-storage-detail", args=[self.storage11.key]))
assert response.status_code == 200
entry = response.data
assert entry["key"] == self.storage11.key
assert entry["value"] == self.storage11.value
response = client.logout()
# Get single entry
client.login(username=user1.username, password=user1.username)
response = client.get(reverse("user-storage-detail", args=[storage11.key]))
assert response.status_code == 200
assert response.data["key"] == storage11.key
assert response.data["value"] == storage11.value
def test_view_an_entry_by_incorrect_user(self, client):
self._load_initial_data()
response = client.login(username=self.user2.username, password=self.user2.username)
response = client.get(reverse("user-storage-detail", args=[self.storage11.key]))
assert response.status_code == 404
response = client.logout()
# Get not existent key
client.login(username=user2.username, password=user2.username)
response = client.get(reverse("user-storage-detail", args=[storage11.key]))
assert response.status_code == 404
def test_view_non_existent_entry(self, client):
self._load_initial_data()
response = client.login(username=self.user1.username, password=self.user1.username)
response = client.get(reverse("user-storage-detail", args=["foo"]))
assert response.status_code == 404
response = client.logout()
response = client.get(reverse("user-storage-detail", args=["foobar"]))
assert response.status_code == 404
client.logout()
class TestCreateStorageEntries(object):
@classmethod
def setup_class(cls):
cls.form = {"key": "foo",
"value": "bar"}
cls.form_without_key = {"value": "bar"}
cls.form_without_value = {"key": "foo"}
def test_create_entries(client):
user1 = factories.UserFactory()
user2 = factories.UserFactory()
storage11 = factories.StorageEntryFactory(owner=user1)
def _load_initial_data(self):
self.user1 = factories.UserFactory()
self.user2 = factories.UserFactory()
self.storage11 = factories.StorageEntryFactory(owner=self.user1)
form = {"key": "foo",
"value": "bar"}
form_without_key = {"value": "bar"}
form_without_value = {"key": "foo"}
error_form = {"key": storage11.key,
"value": "bar"}
def test_create_entry_by_anonymous_user_with_error(self, client):
self._load_initial_data()
response = client.post(reverse("user-storage-list"), self.form)
assert response.status_code == 401
# Create entry by anonymous user
response = client.post(reverse("user-storage-list"), form)
assert response.status_code == 401
def test_create_entry_successfully(self, client):
self._load_initial_data()
response = client.login(username=self.user1.username, password=self.user1.username)
response = client.post(reverse("user-storage-list"), self.form)
assert response.status_code == 201
response = client.get(reverse("user-storage-detail", args=[self.form["key"]]))
assert response.status_code == 200
response = client.logout()
# Create by logged user
client.login(username=user1.username, password=user1.username)
response = client.post(reverse("user-storage-list"), form)
assert response.status_code == 201
response = client.get(reverse("user-storage-detail", args=[form["key"]]))
assert response.status_code == 200
def test_create_entry_with_incorret_form_error(self, client):
self._load_initial_data()
response = client.login(username=self.user1.username, password=self.user1.username)
response = client.post(reverse("user-storage-list"), self.form_without_key)
assert response.status_code == 400
response = client.post(reverse("user-storage-list"), self.form_without_value)
assert response.status_code == 400
response = client.logout()
# Wrong data
client.login(username=user1.username, password=user1.username)
response = client.post(reverse("user-storage-list"), form_without_key)
assert response.status_code == 400
response = client.post(reverse("user-storage-list"), form_without_value)
assert response.status_code == 400
response = client.post(reverse("user-storage-list"), error_form)
assert response.status_code == 400
def test_create_entry_with_integrity_error(self, client):
self._load_initial_data()
response = client.login(username=self.user1.username, password=self.user1.username)
error_form = {"key": self.storage11.key,
"value": "bar"}
response = client.post(reverse("user-storage-list"), error_form)
assert response.status_code == 400
response = client.logout()
client.logout()
class TestUpdateStorageEntries(object):
@classmethod
def setup_class(cls):
cls.form = {"value": "bar"}
def test_update_entries(client):
user1 = factories.UserFactory()
user2 = factories.UserFactory()
storage11 = factories.StorageEntryFactory(owner=user1)
def _load_initial_data(self):
self.user1 = factories.UserFactory()
self.user2 = factories.UserFactory()
self.storage11 = factories.StorageEntryFactory(owner=self.user1)
# Update by anonymous user
form = {"value": "bar", "key": storage11.key}
response = client.put(reverse("user-storage-detail", args=[storage11.key]),
json.dumps(form),
content_type='application/json')
assert response.status_code == 401
def test_update_entry_by_anonymous_user(self, client):
self._load_initial_data()
self.form["key"] = self.storage11.key
response = client.put(reverse("user-storage-detail", args=[self.storage11.key]),
json.dumps(self.form),
content_type='application/json')
assert response.status_code == 401
# Update by logged user
client.login(username=user1.username, password=user1.username)
form = {"value": "bar", "key": storage11.key}
def test_update_entry(self, client):
self._load_initial_data()
response = client.login(username=self.user1.username, password=self.user1.username)
self.form["key"] = self.storage11.key
response = client.put(reverse("user-storage-detail", args=[self.storage11.key]),
json.dumps(self.form),
content_type='application/json')
assert response.status_code == 200
response = client.get(reverse("user-storage-detail", args=[self.storage11.key]))
assert response.status_code == 200
entry = response.data
assert entry["value"] == self.form["value"]
response = client.logout()
response = client.put(reverse("user-storage-detail", args=[storage11.key]),
json.dumps(form),
content_type='application/json')
assert response.status_code == 200
response = client.get(reverse("user-storage-detail", args=[storage11.key]))
assert response.status_code == 200
assert response.data["value"] == form["value"]
def test_update_non_existent_entry(self, client):
self._load_initial_data()
response = client.login(username=self.user1.username, password=self.user1.username)
self.form["key"] = "foo"
response = client.get(reverse("user-storage-detail", args=[self.form["key"]]))
assert response.status_code == 404
response = client.put(reverse("user-storage-detail", args=[self.form["key"]]),
json.dumps(self.form),
content_type='application/json')
assert response.status_code == 201
response = client.get(reverse("user-storage-detail", args=[self.form["key"]]))
assert response.status_code == 200
entry = response.data
assert entry["value"] == self.form["value"]
response = client.logout()
# Update not existing entry
form = {"value": "bar", "key": "foo"}
response = client.get(reverse("user-storage-detail", args=[form["key"]]))
assert response.status_code == 404
response = client.put(reverse("user-storage-detail", args=[form["key"]]),
json.dumps(form),
content_type='application/json')
assert response.status_code == 201
response = client.get(reverse("user-storage-detail", args=[form["key"]]))
assert response.status_code == 200
assert response.data["value"] == form["value"]
client.logout()
class TestDeleteStorageEntries(object):
def _load_initial_data(self):
self.user1 = factories.UserFactory()
self.user2 = factories.UserFactory()
self.storage11 = factories.StorageEntryFactory(owner=self.user1)
def test_delete_entry_by_anonymous_user(self, client):
self._load_initial_data()
response = client.delete(reverse("user-storage-detail", args=[self.storage11.key]))
assert response.status_code == 401
def test_delete_storage_entry(client):
user1 = factories.UserFactory()
user2 = factories.UserFactory()
storage11 = factories.StorageEntryFactory(owner=user1)
def test_delete_entry(self, client):
self._load_initial_data()
response = client.login(username=self.user1.username, password=self.user1.username)
key = self.storage11.key
response = client.delete(reverse("user-storage-detail", args=[key]))
assert response.status_code == 204
response = client.get(reverse("user-storage-detail", args=[key]))
assert response.status_code == 404
response = client.logout()
# Delete by anonumous user
response = client.delete(reverse("user-storage-detail", args=[storage11.key]))
assert response.status_code == 401
def test_delete_entry_by_incorrect_user(self, client):
self._load_initial_data()
response = client.login(username=self.user2.username, password=self.user2.username)
response = client.delete(reverse("user-storage-detail", args=[self.storage11.key]))
assert response.status_code == 404
response = client.logout()
# Delete by logged user
client.login(username=user1.username, password=user1.username)
response = client.delete(reverse("user-storage-detail", args=[storage11.key]))
assert response.status_code == 204
response = client.get(reverse("user-storage-detail", args=[storage11.key]))
assert response.status_code == 404
# Delete not existent entry
response = client.delete(reverse("user-storage-detail", args=["foo"]))
assert response.status_code == 404
client.login(username=user2.username, password=user2.username)
response = client.delete(reverse("user-storage-detail", args=[storage11.key]))
assert response.status_code == 404
client.logout()
def test_delete_non_existent_entry(self, client):
self._load_initial_data()
response = client.login(username=self.user1.username, password=self.user1.username)
response = client.delete(reverse("user-storage-detail", args=["foo"]))
assert response.status_code == 404
response = client.logout()