diff --git a/.travis.yml b/.travis.yml
index e07f042c..2fb3d0b7 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -12,6 +12,7 @@ before_install:
- sudo /etc/init.d/postgresql stop
- sudo apt-get install -y postgresql-9.4
- sudo apt-get install -y postgresql-plpython-9.4
+ - sudo /etc/init.d/postgresql start
- psql -c 'create database taiga;' -U postgres
install:
- travis_retry pip install -r requirements-devel.txt
diff --git a/taiga/base/api/fields.py b/taiga/base/api/fields.py
index a7efeadf..bab90160 100644
--- a/taiga/base/api/fields.py
+++ b/taiga/base/api/fields.py
@@ -613,6 +613,8 @@ class ChoiceField(WritableField):
def validate_user_email_allowed_domains(value):
+ validators.validate_email(value)
+
domain_name = value.split("@")[1]
if settings.USER_EMAIL_ALLOWED_DOMAINS and domain_name not in settings.USER_EMAIL_ALLOWED_DOMAINS:
@@ -627,7 +629,7 @@ class EmailField(CharField):
default_error_messages = {
"invalid": _("Enter a valid email address."),
}
- default_validators = [validators.validate_email, validate_user_email_allowed_domains]
+ default_validators = [validate_user_email_allowed_domains]
def from_native(self, value):
ret = super(EmailField, self).from_native(value)
diff --git a/taiga/projects/attachments/api.py b/taiga/projects/attachments/api.py
index 986c8f19..f2a023c5 100644
--- a/taiga/projects/attachments/api.py
+++ b/taiga/projects/attachments/api.py
@@ -65,6 +65,9 @@ class BaseAttachmentViewSet(HistoryResourceMixin, WatchedResourceMixin,
obj.size = obj.attached_file.size
obj.name = path.basename(obj.attached_file.name)
+ if obj.content_object is None:
+ raise exc.WrongArguments(_("Object id issue isn't exists"))
+
if obj.project_id != obj.content_object.project_id:
raise exc.WrongArguments(_("Project ID not matches between object and project"))
diff --git a/taiga/projects/management/commands/sample_data.py b/taiga/projects/management/commands/sample_data.py
index 23bbf598..7ced0599 100644
--- a/taiga/projects/management/commands/sample_data.py
+++ b/taiga/projects/management/commands/sample_data.py
@@ -16,7 +16,6 @@
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see .
-import random
import datetime
from os import path
from hashlib import sha1
@@ -33,6 +32,7 @@ from sampledatahelper.helper import SampleDataHelper
from taiga.users.models import *
from taiga.permissions.choices import ANON_PERMISSIONS
from taiga.projects.choices import BLOCKED_BY_STAFF
+from taiga.external_apps.models import Application, ApplicationToken
from taiga.projects.models import *
from taiga.projects.milestones.models import *
from taiga.projects.notifications.choices import NotifyLevel
@@ -115,10 +115,12 @@ NUM_TASKS = getattr(settings, "SAMPLE_DATA_NUM_TASKS", (0, 4))
NUM_USS_BACK = getattr(settings, "SAMPLE_DATA_NUM_USS_BACK", (8, 20))
NUM_ISSUES = getattr(settings, "SAMPLE_DATA_NUM_ISSUES", (12, 25))
NUM_WIKI_LINKS = getattr(settings, "SAMPLE_DATA_NUM_WIKI_LINKS", (0, 15))
-NUM_ATTACHMENTS = getattr(settings, "SAMPLE_DATA_NUM_ATTACHMENTS", (0, 4))
+NUM_ATTACHMENTS = getattr(settings, "SAMPLE_DATA_NUM_ATTACHMENTS", (1, 4))
NUM_LIKES = getattr(settings, "SAMPLE_DATA_NUM_LIKES", (0, 10))
NUM_VOTES = getattr(settings, "SAMPLE_DATA_NUM_VOTES", (0, 10))
NUM_WATCHERS = getattr(settings, "SAMPLE_DATA_NUM_PROJECT_WATCHERS", (0, 8))
+NUM_APPLICATIONS = getattr(settings, "SAMPLE_DATA_NUM_APPLICATIONS", (1, 3))
+NUM_APPLICATIONS_TOKENS = getattr(settings, "SAMPLE_DATA_NUM_APPLICATIONS_TOKENS", (1, 3))
FEATURED_PROJECTS_POSITIONS = [0, 1, 2]
LOOKING_FOR_PEOPLE_PROJECTS_POSITIONS = [0, 1, 2]
@@ -181,36 +183,33 @@ class Command(BaseCommand):
project=project,
role=role,
is_admin=self.sd.boolean(),
- token=''.join(random.sample('abcdef0123456789', 10)))
+ token=self.sd.hex_chars(10,10))
if role.computable:
computable_project_roles.add(role)
# added custom attributes
- if self.sd.boolean:
- names = set([self.sd.words(1, 3) for i in range(1, 6)])
- for name in names:
- UserStoryCustomAttribute.objects.create(name=name,
- description=self.sd.words(3, 12),
- type=self.sd.choice(TYPES_CHOICES)[0],
- project=project,
- order=i)
- if self.sd.boolean:
- names = set([self.sd.words(1, 3) for i in range(1, 6)])
- for name in names:
- TaskCustomAttribute.objects.create(name=name,
- description=self.sd.words(3, 12),
- type=self.sd.choice(TYPES_CHOICES)[0],
- project=project,
- order=i)
- if self.sd.boolean:
- names = set([self.sd.words(1, 3) for i in range(1, 6)])
- for name in names:
- IssueCustomAttribute.objects.create(name=name,
+ names = set([self.sd.words(1, 3) for i in range(1, 6)])
+ for name in names:
+ UserStoryCustomAttribute.objects.create(name=name,
description=self.sd.words(3, 12),
type=self.sd.choice(TYPES_CHOICES)[0],
project=project,
order=i)
+ names = set([self.sd.words(1, 3) for i in range(1, 6)])
+ for name in names:
+ TaskCustomAttribute.objects.create(name=name,
+ description=self.sd.words(3, 12),
+ type=self.sd.choice(TYPES_CHOICES)[0],
+ project=project,
+ order=i)
+ names = set([self.sd.words(1, 3) for i in range(1, 6)])
+ for name in names:
+ IssueCustomAttribute.objects.create(name=name,
+ description=self.sd.words(3, 12),
+ type=self.sd.choice(TYPES_CHOICES)[0],
+ project=project,
+ order=i)
# If the project isn't empty
if x not in empty_projects_range:
@@ -272,6 +271,7 @@ class Command(BaseCommand):
self.create_likes(project)
+
def create_attachment(self, obj, order):
attached_file = self.sd.file_from_directory(*ATTACHMENT_SAMPLE_DATA)
membership = self.sd.db_object_from_queryset(obj.project.memberships
@@ -347,7 +347,7 @@ class Command(BaseCommand):
bug.save()
custom_attributes_values = {str(ca.id): self.get_custom_attributes_value(ca.type) for ca
- in project.issuecustomattributes.all() if self.sd.boolean()}
+ in project.issuecustomattributes.all().order_by('id') if self.sd.boolean()}
if custom_attributes_values:
bug.custom_attributes_values.attributes_values = custom_attributes_values
bug.custom_attributes_values.save()
@@ -399,7 +399,7 @@ class Command(BaseCommand):
task.save()
custom_attributes_values = {str(ca.id): self.get_custom_attributes_value(ca.type) for ca
- in project.taskcustomattributes.all() if self.sd.boolean()}
+ in project.taskcustomattributes.all().order_by('id') if self.sd.boolean()}
if custom_attributes_values:
task.custom_attributes_values.attributes_values = custom_attributes_values
task.custom_attributes_values.save()
@@ -447,7 +447,7 @@ class Command(BaseCommand):
us.save()
custom_attributes_values = {str(ca.id): self.get_custom_attributes_value(ca.type) for ca
- in project.userstorycustomattributes.all() if self.sd.boolean()}
+ in project.userstorycustomattributes.all().order_by('id') if self.sd.boolean()}
if custom_attributes_values:
us.custom_attributes_values.attributes_values = custom_attributes_values
us.custom_attributes_values.save()
@@ -503,7 +503,7 @@ class Command(BaseCommand):
project = Project.objects.create(slug='project-%s'%(counter),
name='Project Example {0}'.format(counter),
description='Project example {0} description'.format(counter),
- owner=random.choice(self.users),
+ owner=self.sd.choice(self.users),
is_private=is_private,
anon_permissions=anon_permissions,
public_permissions=public_permissions,
@@ -533,7 +533,7 @@ class Command(BaseCommand):
user = User.objects.create(username=username,
full_name=full_name,
email=email,
- token=''.join(random.sample('abcdef0123456789', 10)),
+ token=self.sd.hex_chars(10,10),
color=self.sd.choice(COLOR_CHOICES))
user.set_password('123123')
diff --git a/taiga/projects/models.py b/taiga/projects/models.py
index 2c0f4027..7b3c218d 100644
--- a/taiga/projects/models.py
+++ b/taiga/projects/models.py
@@ -180,6 +180,7 @@ class Project(ProjectDefaults, TaggedMixin, TagsColorsdMixin, models.Model):
creation_template = models.ForeignKey("projects.ProjectTemplate",
related_name="projects", null=True,
+ on_delete=models.SET_NULL,
blank=True, default=None,
verbose_name=_("creation template"))
diff --git a/taiga/projects/notifications/api.py b/taiga/projects/notifications/api.py
index cd8f564e..9936ae52 100644
--- a/taiga/projects/notifications/api.py
+++ b/taiga/projects/notifications/api.py
@@ -21,9 +21,7 @@ from django.db.models import Q
from taiga.base.api import ModelCrudViewSet
from taiga.projects.notifications.choices import NotifyLevel
-from taiga.projects.notifications.models import Watched
from taiga.projects.models import Project
-from taiga.users import services as user_services
from . import serializers
from . import models
from . import permissions
diff --git a/taiga/users/filters.py b/taiga/users/filters.py
index 4e4dc116..46dd88ac 100644
--- a/taiga/users/filters.py
+++ b/taiga/users/filters.py
@@ -16,11 +16,10 @@
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see .
-from django.apps import apps
-
from taiga.base.filters import PermissionBasedFilterBackend
from . import services
+
class ContactsFilterBackend(PermissionBasedFilterBackend):
def filter_queryset(self, user, request, queryset, view):
qs = queryset.filter(is_active=True)
diff --git a/taiga/users/serializers.py b/taiga/users/serializers.py
index e2e55ea9..a720e46e 100644
--- a/taiga/users/serializers.py
+++ b/taiga/users/serializers.py
@@ -24,10 +24,9 @@ from taiga.base.fields import Field, MethodField, I18NField
from taiga.base.utils.thumbnails import get_thumbnail_url
from taiga.projects.models import Project
-from .services import get_user_photo_url, get_big_photo_url, get_user_big_photo_url
+from .services import get_user_photo_url, get_user_big_photo_url
from taiga.users.gravatar import get_user_gravatar_id
from taiga.users.models import User
-from collections import namedtuple
######################################################
@@ -142,7 +141,7 @@ class RoleSerializer(serializers.LightSerializer):
project = Field(attr="project_id")
order = Field()
computable = Field()
- permissions = Field()
+ permissions = Field()
members_count = MethodField()
def get_members_count(self, obj):