Adding max_private_projects and max_public_projects support

remotes/origin/logger
Alejandro Alonso 2016-01-20 15:13:22 +01:00 committed by David Barragán Merino
parent 273d94f347
commit b8fd768d01
8 changed files with 192 additions and 5 deletions

View File

@ -524,6 +524,9 @@ FRONT_SITEMAP_CACHE_TIMEOUT = 24*60*60 # In second
EXTRA_BLOCKING_CODES = [] EXTRA_BLOCKING_CODES = []
MAX_PRIVATE_PROJECTS_PER_USER = None # None == no limit
MAX_PUBLIC_PROJECTS_PER_USER = None # None == no limit
from .sr import * from .sr import *

View File

@ -51,6 +51,7 @@ from taiga.projects.tasks.models import Task
from taiga.projects.issues.models import Issue from taiga.projects.issues.models import Issue
from taiga.projects.likes.mixins.viewsets import LikedResourceMixin, FansViewSetMixin from taiga.projects.likes.mixins.viewsets import LikedResourceMixin, FansViewSetMixin
from taiga.permissions import service as permissions_service from taiga.permissions import service as permissions_service
from taiga.users import services as users_service
from . import filters as project_filters from . import filters as project_filters
from . import models from . import models
@ -342,9 +343,12 @@ 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
if not users_service.has_available_slot_for_project(user, is_private=obj.is_private):
raise exc.BadRequest(_("The user can't have more projects of this type"))
if not obj.id: if not obj.id:
obj.owner = self.request.user obj.owner = user
# TODO REFACTOR THIS
obj.template = self.request.QUERY_PARAMS.get('template', None) obj.template = self.request.QUERY_PARAMS.get('template', None)
self._set_base_permissions(obj) self._set_base_permissions(obj)

View File

@ -50,7 +50,7 @@ class UserAdmin(DjangoUserAdmin):
(None, {'fields': ('username', 'password')}), (None, {'fields': ('username', 'password')}),
(_('Personal info'), {'fields': ('full_name', 'email', 'bio', 'photo')}), (_('Personal info'), {'fields': ('full_name', 'email', 'bio', 'photo')}),
(_('Extra info'), {'fields': ('color', 'lang', 'timezone', 'token', 'colorize_tags', 'email_token', 'new_email')}), (_('Extra info'), {'fields': ('color', 'lang', 'timezone', 'token', 'colorize_tags', 'email_token', 'new_email')}),
(_('Permissions'), {'fields': ('is_active', 'is_superuser',)}), (_('Permissions'), {'fields': ('is_active', 'is_superuser', 'max_private_projects', 'max_public_projects')}),
(_('Important dates'), {'fields': ('last_login', 'date_joined')}), (_('Important dates'), {'fields': ('last_login', 'date_joined')}),
) )
form = UserChangeForm form = UserChangeForm

View File

@ -0,0 +1,24 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('users', '0014_auto_20151005_1357'),
]
operations = [
migrations.AddField(
model_name='user',
name='max_private_projects',
field=models.IntegerField(null=True, verbose_name='max number of private projects owned', default=None, blank=True),
),
migrations.AddField(
model_name='user',
name='max_public_projects',
field=models.IntegerField(null=True, verbose_name='max number of public projects owned', default=None, blank=True),
),
]

View File

@ -25,6 +25,7 @@ import uuid
from unidecode import unidecode from unidecode import unidecode
from django.apps import apps from django.apps import apps
from django.conf import settings
from django.contrib.contenttypes.models import ContentType from django.contrib.contenttypes.models import ContentType
from django.db import models from django.db import models
from django.dispatch import receiver from django.dispatch import receiver
@ -140,6 +141,11 @@ class User(AbstractBaseUser, PermissionsMixin):
new_email = models.EmailField(_('new email address'), null=True, blank=True) new_email = models.EmailField(_('new email address'), null=True, blank=True)
is_system = models.BooleanField(null=False, blank=False, default=False) is_system = models.BooleanField(null=False, blank=False, default=False)
max_private_projects = models.IntegerField(null=True, blank=True, default=settings.MAX_PRIVATE_PROJECTS_PER_USER, verbose_name='max number of private projects owned')
max_public_projects = models.IntegerField(null=True, blank=True, default=settings.MAX_PUBLIC_PROJECTS_PER_USER, verbose_name='max number of public projects owned')
_cached_memberships = None _cached_memberships = None
_cached_liked_ids = None _cached_liked_ids = None
_cached_watched_ids = None _cached_watched_ids = None

View File

@ -104,14 +104,27 @@ class UserSerializer(serializers.ModelSerializer):
return ContactProjectDetailSerializer(projects, many=True).data return ContactProjectDetailSerializer(projects, many=True).data
class UserAdminSerializer(UserSerializer): class UserAdminSerializer(UserSerializer):
total_private_projects = serializers.SerializerMethodField("get_total_private_projects")
total_public_projects = serializers.SerializerMethodField("get_total_public_projects")
class Meta: class Meta:
model = User model = User
# IMPORTANT: Maintain the UserSerializer Meta up to date # IMPORTANT: Maintain the UserSerializer Meta up to date
# with this info (including here the email) # with this info (including here the email)
fields = ("id", "username", "full_name", "full_name_display", "email", fields = ("id", "username", "full_name", "full_name_display", "email",
"color", "bio", "lang", "theme", "timezone", "is_active", "photo", "color", "bio", "lang", "theme", "timezone", "is_active", "photo",
"big_photo") "big_photo",
read_only_fields = ("id", "email") "max_private_projects", "max_public_projects",
"total_private_projects", "total_public_projects")
read_only_fields = ("id", "email",
"max_private_projects", "max_public_projects")
def get_total_private_projects(self, user):
return user.owned_projects.filter(is_private=True).count()
def get_total_public_projects(self, user):
return user.owned_projects.filter(is_private=False).count()
class UserBasicInfoSerializer(UserSerializer): class UserBasicInfoSerializer(UserSerializer):

View File

@ -572,3 +572,16 @@ def get_voted_list(for_user, from_user, type=None, q=None):
dict(zip([col[0] for col in desc], row)) dict(zip([col[0] for col in desc], row))
for row in cursor.fetchall() for row in cursor.fetchall()
] ]
def has_available_slot_for_project(user, is_private=False):
if is_private:
if user.max_private_projects is None:
return True
return user.owned_projects.filter(is_private=True).count() < user.max_private_projects
if user.max_public_projects is None:
return True
return user.owned_projects.filter(is_private=False).count() < user.max_public_projects

View File

@ -43,6 +43,130 @@ def test_create_project(client):
assert response.status_code == 201 assert response.status_code == 201
def test_create_private_project_without_enough_private_projects_slots(client):
user = f.create_user(max_private_projects=0)
url = reverse("projects-list")
data = {
"name": "project name",
"description": "project description",
"is_private": True
}
client.login(user)
response = client.json.post(url, json.dumps(data))
assert response.status_code == 400
assert "can't have more projects" in response.data["_error_message"]
def test_create_public_project_without_enough_public_projects_slots(client):
user = f.create_user(max_public_projects=0)
url = reverse("projects-list")
data = {
"name": "project name",
"description": "project description",
"is_private": False
}
client.login(user)
response = client.json.post(url, json.dumps(data))
assert response.status_code == 400
assert "can't have more projects" in response.data["_error_message"]
def test_change_project_from_private_to_public_without_enough_public_projects_slots(client):
project = f.create_project(is_private=True, owner__max_public_projects=0)
f.MembershipFactory(user=project.owner, project=project, is_owner=True)
url = reverse("projects-detail", kwargs={"pk": project.pk})
data = {
"is_private": False
}
client.login(project.owner)
response = client.json.patch(url, json.dumps(data))
assert response.status_code == 400
assert "can't have more projects" in response.data["_error_message"]
def test_change_project_from_public_to_private_without_enough_private_projects_slots(client):
project = f.create_project(is_private=False, owner__max_private_projects=0)
f.MembershipFactory(user=project.owner, project=project, is_owner=True)
url = reverse("projects-detail", kwargs={"pk": project.pk})
data = {
"is_private": True
}
client.login(project.owner)
response = client.json.patch(url, json.dumps(data))
assert response.status_code == 400
assert "can't have more projects" in response.data["_error_message"]
def test_create_private_project_with_enough_private_projects_slots(client):
user = f.create_user(max_private_projects=1)
url = reverse("projects-list")
data = {
"name": "project name",
"description": "project description",
"is_private": True
}
client.login(user)
response = client.json.post(url, json.dumps(data))
assert response.status_code == 201
def test_create_public_project_with_enough_public_projects_slots(client):
user = f.create_user(max_public_projects=1)
url = reverse("projects-list")
data = {
"name": "project name",
"description": "project description",
"is_private": False
}
client.login(user)
response = client.json.post(url, json.dumps(data))
assert response.status_code == 201
def test_change_project_from_private_to_public_with_enough_public_projects_slots(client):
project = f.create_project(is_private=True, owner__max_public_projects=1)
f.MembershipFactory(user=project.owner, project=project, is_owner=True)
url = reverse("projects-detail", kwargs={"pk": project.pk})
data = {
"is_private": False
}
client.login(project.owner)
response = client.json.patch(url, json.dumps(data))
assert response.status_code == 200
def test_change_project_from_public_to_private_with_enough_private_projects_slots(client):
project = f.create_project(is_private=False, owner__max_private_projects=1)
f.MembershipFactory(user=project.owner, project=project, is_owner=True)
url = reverse("projects-detail", kwargs={"pk": project.pk})
data = {
"is_private": True
}
client.login(project.owner)
response = client.json.patch(url, json.dumps(data))
assert response.status_code == 200
def test_partially_update_project(client): def test_partially_update_project(client):
project = f.create_project() project = f.create_project()
f.MembershipFactory(user=project.owner, project=project, is_owner=True) f.MembershipFactory(user=project.owner, project=project, is_owner=True)