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..f474de86 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 @@ -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/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):