Merge pull request #680 from taigaio/issue/4032/change_privacity_validation_error
Fix issue #4032: Wrong validation when change the project privacityremotes/origin/issue/4795/notification_even_they_are_disabled
commit
28efb6dd68
|
@ -94,7 +94,7 @@ class ProjectImporterViewSet(mixins.ImportThrottlingPolicyMixin, CreateModelMixi
|
||||||
is_private = data.get('is_private', False)
|
is_private = data.get('is_private', False)
|
||||||
(enough_slots, not_enough_slots_error) = users_service.has_available_slot_for_project(
|
(enough_slots, not_enough_slots_error) = users_service.has_available_slot_for_project(
|
||||||
self.request.user,
|
self.request.user,
|
||||||
project=Project(is_private=is_private, id=None)
|
Project(is_private=is_private, id=None)
|
||||||
)
|
)
|
||||||
if not enough_slots:
|
if not enough_slots:
|
||||||
raise exc.NotEnoughSlotsForProject(is_private, 1, not_enough_slots_error)
|
raise exc.NotEnoughSlotsForProject(is_private, 1, not_enough_slots_error)
|
||||||
|
@ -115,11 +115,11 @@ class ProjectImporterViewSet(mixins.ImportThrottlingPolicyMixin, CreateModelMixi
|
||||||
|
|
||||||
# Create memberships
|
# Create memberships
|
||||||
if "memberships" in data:
|
if "memberships" in data:
|
||||||
members = len(data['memberships'])
|
members = len([m for m in data.get("memberships", []) if m.get("email", None) != data["owner"]])
|
||||||
(enough_slots, not_enough_slots_error) = users_service.has_available_slot_for_project(
|
(enough_slots, not_enough_slots_error) = users_service.has_available_slot_for_project(
|
||||||
self.request.user,
|
self.request.user,
|
||||||
project=Project(is_private=is_private, id=None),
|
Project(is_private=is_private, id=None),
|
||||||
members=max(members, 1)
|
members
|
||||||
)
|
)
|
||||||
if not enough_slots:
|
if not enough_slots:
|
||||||
raise exc.NotEnoughSlotsForProject(is_private, max(members, 1), not_enough_slots_error)
|
raise exc.NotEnoughSlotsForProject(is_private, max(members, 1), not_enough_slots_error)
|
||||||
|
@ -223,16 +223,18 @@ class ProjectImporterViewSet(mixins.ImportThrottlingPolicyMixin, CreateModelMixi
|
||||||
except Exception:
|
except Exception:
|
||||||
raise exc.WrongArguments(_("Invalid dump format"))
|
raise exc.WrongArguments(_("Invalid dump format"))
|
||||||
|
|
||||||
user = request.user
|
|
||||||
slug = dump.get('slug', None)
|
slug = dump.get('slug', None)
|
||||||
if slug is not None and Project.objects.filter(slug=slug).exists():
|
if slug is not None and Project.objects.filter(slug=slug).exists():
|
||||||
del dump['slug']
|
del dump['slug']
|
||||||
|
|
||||||
members = len(dump.get("memberships", []))
|
user = request.user
|
||||||
|
dump['owner'] = user.email
|
||||||
|
|
||||||
|
members = len([m for m in dump.get("memberships", []) if m.get("email", None) != dump["owner"]])
|
||||||
(enough_slots, not_enough_slots_error) = users_service.has_available_slot_for_project(
|
(enough_slots, not_enough_slots_error) = users_service.has_available_slot_for_project(
|
||||||
user,
|
user,
|
||||||
project=Project(is_private=is_private, id=None),
|
Project(is_private=is_private, id=None),
|
||||||
members=max(members, 1)
|
members
|
||||||
)
|
)
|
||||||
if not enough_slots:
|
if not enough_slots:
|
||||||
raise exc.NotEnoughSlotsForProject(is_private, max(members, 1), not_enough_slots_error)
|
raise exc.NotEnoughSlotsForProject(is_private, max(members, 1), not_enough_slots_error)
|
||||||
|
|
|
@ -91,11 +91,11 @@ def store_tags_colors(project, data):
|
||||||
def dict_to_project(data, owner=None):
|
def dict_to_project(data, owner=None):
|
||||||
if owner:
|
if owner:
|
||||||
data["owner"] = owner.email
|
data["owner"] = owner.email
|
||||||
members = len(data.get("memberships", []))
|
members = len([m for m in data.get("memberships", []) if m.get("email", None) != data["owner"]])
|
||||||
(enough_slots, not_enough_slots_error) = users_service.has_available_slot_for_project(
|
(enough_slots, not_enough_slots_error) = users_service.has_available_slot_for_project(
|
||||||
owner,
|
owner,
|
||||||
project=Project(is_private=data.get("is_private", False), id=None),
|
Project(is_private=data.get("is_private", False), id=None),
|
||||||
members=members
|
members
|
||||||
)
|
)
|
||||||
if not enough_slots:
|
if not enough_slots:
|
||||||
raise TaigaImportError(not_enough_slots_error)
|
raise TaigaImportError(not_enough_slots_error)
|
||||||
|
|
|
@ -380,8 +380,7 @@ class ProjectViewSet(LikedResourceMixin, HistoryResourceMixin,
|
||||||
|
|
||||||
(enough_slots, not_enough_slots_error) = users_service.has_available_slot_for_project(
|
(enough_slots, not_enough_slots_error) = users_service.has_available_slot_for_project(
|
||||||
request.user,
|
request.user,
|
||||||
project=project,
|
project,
|
||||||
members=0
|
|
||||||
)
|
)
|
||||||
if not enough_slots:
|
if not enough_slots:
|
||||||
members = project.memberships.count()
|
members = project.memberships.count()
|
||||||
|
@ -419,16 +418,17 @@ class ProjectViewSet(LikedResourceMixin, HistoryResourceMixin,
|
||||||
permissions_service.set_base_permissions_for_project(obj)
|
permissions_service.set_base_permissions_for_project(obj)
|
||||||
|
|
||||||
def pre_save(self, obj):
|
def pre_save(self, obj):
|
||||||
user = self.request.user
|
|
||||||
(enough_slots, not_enough_slots_error) = users_service.has_available_slot_for_project(user, project=obj)
|
|
||||||
members = max(obj.memberships.count(), 1)
|
|
||||||
if not enough_slots:
|
|
||||||
raise exc.NotEnoughSlotsForProject(obj.is_private, members, not_enough_slots_error)
|
|
||||||
|
|
||||||
if not obj.id:
|
if not obj.id:
|
||||||
obj.owner = user
|
obj.owner = self.request.user
|
||||||
obj.template = self.request.QUERY_PARAMS.get('template', None)
|
obj.template = self.request.QUERY_PARAMS.get('template', None)
|
||||||
|
|
||||||
|
# Validate if the owner have enought slots to create or update the project
|
||||||
|
# TODO: Move to the ProjectAdminSerializer
|
||||||
|
(enough_slots, not_enough_slots_error) = users_service.has_available_slot_for_project(obj.owner, obj)
|
||||||
|
if not enough_slots:
|
||||||
|
members = max(obj.memberships.count(), 1)
|
||||||
|
raise exc.NotEnoughSlotsForProject(obj.is_private, members, not_enough_slots_error)
|
||||||
|
|
||||||
self._set_base_permissions(obj)
|
self._set_base_permissions(obj)
|
||||||
super().pre_save(obj)
|
super().pre_save(obj)
|
||||||
|
|
||||||
|
@ -635,8 +635,8 @@ class MembershipViewSet(BlockedByProjectMixin, ModelCrudViewSet):
|
||||||
members = len(data["bulk_memberships"])
|
members = len(data["bulk_memberships"])
|
||||||
(enough_slots, not_enough_slots_error) = users_service.has_available_slot_for_project(
|
(enough_slots, not_enough_slots_error) = users_service.has_available_slot_for_project(
|
||||||
project.owner,
|
project.owner,
|
||||||
project=project,
|
project,
|
||||||
members=members
|
members
|
||||||
)
|
)
|
||||||
if not enough_slots:
|
if not enough_slots:
|
||||||
raise exc.NotEnoughSlotsForProject(project.is_private, members, not_enough_slots_error)
|
raise exc.NotEnoughSlotsForProject(project.is_private, members, not_enough_slots_error)
|
||||||
|
@ -672,8 +672,8 @@ class MembershipViewSet(BlockedByProjectMixin, ModelCrudViewSet):
|
||||||
members = 1
|
members = 1
|
||||||
(enough_slots, not_enough_slots_error) = users_service.has_available_slot_for_project(
|
(enough_slots, not_enough_slots_error) = users_service.has_available_slot_for_project(
|
||||||
self.request.user,
|
self.request.user,
|
||||||
project=obj.project,
|
obj.project,
|
||||||
members=members
|
members
|
||||||
)
|
)
|
||||||
if not enough_slots:
|
if not enough_slots:
|
||||||
raise exc.NotEnoughSlotsForProject(obj.project.is_private, members, not_enough_slots_error)
|
raise exc.NotEnoughSlotsForProject(obj.project.is_private, members, not_enough_slots_error)
|
||||||
|
|
|
@ -575,41 +575,60 @@ def get_voted_list(for_user, from_user, type=None, q=None):
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
def has_available_slot_for_project(user, project, members=1):
|
def has_available_slot_for_project(user, project, new_members=0):
|
||||||
|
# TODO: Refactor: Create one service for every type of action and move to project services
|
||||||
|
#
|
||||||
|
# - has_available_slot_to_create_new_project()
|
||||||
|
# - has_available_slot_to_update_this_project()
|
||||||
|
# - has_available_slot_to_transfer_this_project()
|
||||||
|
# - has_available_slot_to_import_this_project()
|
||||||
|
# - has_available_slot_to_add_members_to_this_project()
|
||||||
|
|
||||||
(enough, error) = _has_available_slot_for_project_type(user, project)
|
(enough, error) = _has_available_slot_for_project_type(user, project)
|
||||||
if not enough:
|
if not enough:
|
||||||
return (enough, error)
|
return (enough, error)
|
||||||
return _has_available_slot_for_project_members(user, project, members)
|
return _has_available_slot_for_project_members(user, project, new_members)
|
||||||
|
|
||||||
|
|
||||||
def _has_available_slot_for_project_type(user, project):
|
def _has_available_slot_for_project_type(user, project):
|
||||||
if project.is_private:
|
if project.is_private:
|
||||||
if user.max_private_projects is None:
|
if user.max_private_projects is None:
|
||||||
return (True, None)
|
return (True, None)
|
||||||
elif user.owned_projects.filter(is_private=True).exclude(id=project.id).count() < user.max_private_projects:
|
|
||||||
|
current_private_projects = user.owned_projects.filter(is_private=True).exclude(id=project.id).count()
|
||||||
|
if current_private_projects < user.max_private_projects:
|
||||||
return (True, None)
|
return (True, None)
|
||||||
|
|
||||||
return (False, _("You can't have more private projects"))
|
return (False, _("You can't have more private projects"))
|
||||||
|
|
||||||
else:
|
else:
|
||||||
if user.max_public_projects is None:
|
if user.max_public_projects is None:
|
||||||
return (True, None)
|
return (True, None)
|
||||||
elif user.owned_projects.filter(is_private=False).exclude(id=project.id).count() < user.max_public_projects:
|
|
||||||
|
current_public_project = user.owned_projects.filter(is_private=False).exclude(id=project.id).count()
|
||||||
|
if current_public_project < user.max_public_projects:
|
||||||
return (True, None)
|
return (True, None)
|
||||||
|
|
||||||
return (False, _("You can't have more public projects"))
|
return (False, _("You can't have more public projects"))
|
||||||
|
|
||||||
|
|
||||||
|
def _has_available_slot_for_project_members(user, project, new_members):
|
||||||
def _has_available_slot_for_project_members(user, project, members):
|
current_memberships = max(project.memberships.count(), 1)
|
||||||
current_memberships = project.memberships.count()
|
|
||||||
|
|
||||||
if project.is_private:
|
if project.is_private:
|
||||||
if user.max_memberships_private_projects is None:
|
if user.max_memberships_private_projects is None:
|
||||||
return (True, None)
|
return (True, None)
|
||||||
elif current_memberships + members <= user.max_memberships_private_projects:
|
|
||||||
|
if current_memberships + new_members <= user.max_memberships_private_projects:
|
||||||
return (True, None)
|
return (True, None)
|
||||||
|
|
||||||
return (False, _("You have reached your current limit of memberships for private projects"))
|
return (False, _("You have reached your current limit of memberships for private projects"))
|
||||||
|
|
||||||
else:
|
else:
|
||||||
if user.max_memberships_public_projects is None:
|
if user.max_memberships_public_projects is None:
|
||||||
return (True, None)
|
return (True, None)
|
||||||
elif current_memberships + members <= user.max_memberships_public_projects:
|
|
||||||
|
if current_memberships + new_members <= user.max_memberships_public_projects:
|
||||||
return (True, None)
|
return (True, None)
|
||||||
|
|
||||||
return (False, _("You have reached your current limit of memberships for public projects"))
|
return (False, _("You have reached your current limit of memberships for public projects"))
|
||||||
|
|
|
@ -1501,3 +1501,87 @@ def test_valid_dump_import_without_slug(client):
|
||||||
|
|
||||||
response = client.post(url, {'dump': data})
|
response = client.post(url, {'dump': data})
|
||||||
assert response.status_code == 201
|
assert response.status_code == 201
|
||||||
|
|
||||||
|
|
||||||
|
def test_valid_dump_import_with_the_limit_of_membership_whit_you_for_private_project(client):
|
||||||
|
user = f.UserFactory.create(max_memberships_private_projects=5)
|
||||||
|
client.login(user)
|
||||||
|
|
||||||
|
url = reverse("importer-load-dump")
|
||||||
|
|
||||||
|
data = ContentFile(bytes(json.dumps({
|
||||||
|
"slug": "private-project-with-memberships-limit-with-you",
|
||||||
|
"name": "Valid project",
|
||||||
|
"description": "Valid project desc",
|
||||||
|
"is_private": True,
|
||||||
|
"memberships": [
|
||||||
|
{
|
||||||
|
"email": user.email,
|
||||||
|
"role": "Role",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"email": "test2@test.com",
|
||||||
|
"role": "Role",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"email": "test3@test.com",
|
||||||
|
"role": "Role",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"email": "test4@test.com",
|
||||||
|
"role": "Role",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"email": "test5@test.com",
|
||||||
|
"role": "Role",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"roles": [{"name": "Role"}]
|
||||||
|
}), "utf-8"))
|
||||||
|
data.name = "test"
|
||||||
|
|
||||||
|
response = client.post(url, {'dump': data})
|
||||||
|
assert response.status_code == 201
|
||||||
|
assert Project.objects.filter(slug="private-project-with-memberships-limit-with-you").count() == 1
|
||||||
|
|
||||||
|
|
||||||
|
def test_valid_dump_import_with_the_limit_of_membership_whit_you_for_public_project(client):
|
||||||
|
user = f.UserFactory.create(max_memberships_public_projects=5)
|
||||||
|
client.login(user)
|
||||||
|
|
||||||
|
url = reverse("importer-load-dump")
|
||||||
|
|
||||||
|
data = ContentFile(bytes(json.dumps({
|
||||||
|
"slug": "public-project-with-memberships-limit-with-you",
|
||||||
|
"name": "Valid project",
|
||||||
|
"description": "Valid project desc",
|
||||||
|
"is_private": False,
|
||||||
|
"memberships": [
|
||||||
|
{
|
||||||
|
"email": user.email,
|
||||||
|
"role": "Role",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"email": "test2@test.com",
|
||||||
|
"role": "Role",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"email": "test3@test.com",
|
||||||
|
"role": "Role",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"email": "test4@test.com",
|
||||||
|
"role": "Role",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"email": "test5@test.com",
|
||||||
|
"role": "Role",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"roles": [{"name": "Role"}]
|
||||||
|
}), "utf-8"))
|
||||||
|
data.name = "test"
|
||||||
|
|
||||||
|
response = client.post(url, {'dump': data})
|
||||||
|
assert response.status_code == 201
|
||||||
|
assert Project.objects.filter(slug="public-project-with-memberships-limit-with-you").count() == 1
|
||||||
|
|
Loading…
Reference in New Issue