Migrating users serializers and validators
parent
39075288ac
commit
7d2b6c34ce
|
@ -25,3 +25,7 @@ def dict_sum(*args):
|
||||||
assert isinstance(arg, dict)
|
assert isinstance(arg, dict)
|
||||||
result += collections.Counter(arg)
|
result += collections.Counter(arg)
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
def into_namedtuple(dictionary):
|
||||||
|
return collections.namedtuple('GenericDict', dictionary.keys())(**dictionary)
|
||||||
|
|
|
@ -19,7 +19,6 @@
|
||||||
import uuid
|
import uuid
|
||||||
|
|
||||||
from django.apps import apps
|
from django.apps import apps
|
||||||
from django.db.models import Q, F
|
|
||||||
from django.utils.translation import ugettext as _
|
from django.utils.translation import ugettext as _
|
||||||
from django.core.validators import validate_email
|
from django.core.validators import validate_email
|
||||||
from django.core.exceptions import ValidationError
|
from django.core.exceptions import ValidationError
|
||||||
|
@ -28,21 +27,21 @@ from django.conf import settings
|
||||||
from taiga.base import exceptions as exc
|
from taiga.base import exceptions as exc
|
||||||
from taiga.base import filters
|
from taiga.base import filters
|
||||||
from taiga.base import response
|
from taiga.base import response
|
||||||
|
from taiga.base.utils.dicts import into_namedtuple
|
||||||
from taiga.auth.tokens import get_user_for_token
|
from taiga.auth.tokens import get_user_for_token
|
||||||
from taiga.base.decorators import list_route
|
from taiga.base.decorators import list_route
|
||||||
from taiga.base.decorators import detail_route
|
from taiga.base.decorators import detail_route
|
||||||
from taiga.base.api import ModelCrudViewSet
|
from taiga.base.api import ModelCrudViewSet
|
||||||
from taiga.base.api.mixins import BlockedByProjectMixin
|
from taiga.base.api.mixins import BlockedByProjectMixin
|
||||||
from taiga.base.filters import PermissionBasedFilterBackend
|
|
||||||
from taiga.base.api.utils import get_object_or_404
|
from taiga.base.api.utils import get_object_or_404
|
||||||
from taiga.base.filters import MembersFilterBackend
|
from taiga.base.filters import MembersFilterBackend
|
||||||
from taiga.base.mails import mail_builder
|
from taiga.base.mails import mail_builder
|
||||||
from taiga.projects.votes import services as votes_service
|
|
||||||
from taiga.users.services import get_user_by_username_or_email
|
from taiga.users.services import get_user_by_username_or_email
|
||||||
from easy_thumbnails.source_generators import pil_image
|
from easy_thumbnails.source_generators import pil_image
|
||||||
|
|
||||||
from . import models
|
from . import models
|
||||||
from . import serializers
|
from . import serializers
|
||||||
|
from . import validators
|
||||||
from . import permissions
|
from . import permissions
|
||||||
from . import filters as user_filters
|
from . import filters as user_filters
|
||||||
from . import services
|
from . import services
|
||||||
|
@ -53,6 +52,8 @@ class UsersViewSet(ModelCrudViewSet):
|
||||||
permission_classes = (permissions.UserPermission,)
|
permission_classes = (permissions.UserPermission,)
|
||||||
admin_serializer_class = serializers.UserAdminSerializer
|
admin_serializer_class = serializers.UserAdminSerializer
|
||||||
serializer_class = serializers.UserSerializer
|
serializer_class = serializers.UserSerializer
|
||||||
|
admin_validator_class = validators.UserAdminValidator
|
||||||
|
validator_class = validators.UserValidator
|
||||||
queryset = models.User.objects.all().prefetch_related("memberships")
|
queryset = models.User.objects.all().prefetch_related("memberships")
|
||||||
filter_backends = (MembersFilterBackend,)
|
filter_backends = (MembersFilterBackend,)
|
||||||
|
|
||||||
|
@ -64,6 +65,14 @@ class UsersViewSet(ModelCrudViewSet):
|
||||||
|
|
||||||
return self.serializer_class
|
return self.serializer_class
|
||||||
|
|
||||||
|
def get_validator_class(self):
|
||||||
|
if self.action in ["partial_update", "update", "retrieve", "by_username"]:
|
||||||
|
user = self.object
|
||||||
|
if self.request.user == user or self.request.user.is_superuser:
|
||||||
|
return self.admin_validator_class
|
||||||
|
|
||||||
|
return self.validator_class
|
||||||
|
|
||||||
def create(self, *args, **kwargs):
|
def create(self, *args, **kwargs):
|
||||||
raise exc.NotSupported()
|
raise exc.NotSupported()
|
||||||
|
|
||||||
|
@ -86,7 +95,7 @@ class UsersViewSet(ModelCrudViewSet):
|
||||||
serializer = self.get_serializer(self.object)
|
serializer = self.get_serializer(self.object)
|
||||||
return response.Ok(serializer.data)
|
return response.Ok(serializer.data)
|
||||||
|
|
||||||
#TODO: commit_on_success
|
# TODO: commit_on_success
|
||||||
def partial_update(self, request, *args, **kwargs):
|
def partial_update(self, request, *args, **kwargs):
|
||||||
"""
|
"""
|
||||||
We must detect if the user is trying to change his email so we can
|
We must detect if the user is trying to change his email so we can
|
||||||
|
@ -96,12 +105,10 @@ class UsersViewSet(ModelCrudViewSet):
|
||||||
user = self.get_object()
|
user = self.get_object()
|
||||||
self.check_permissions(request, "update", user)
|
self.check_permissions(request, "update", user)
|
||||||
|
|
||||||
ret = super().partial_update(request, *args, **kwargs)
|
|
||||||
|
|
||||||
new_email = request.DATA.get('email', None)
|
new_email = request.DATA.get('email', None)
|
||||||
if new_email is not None:
|
if new_email is not None:
|
||||||
valid_new_email = True
|
valid_new_email = True
|
||||||
duplicated_email = models.User.objects.filter(email = new_email).exists()
|
duplicated_email = models.User.objects.filter(email=new_email).exists()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
validate_email(new_email)
|
validate_email(new_email)
|
||||||
|
@ -115,14 +122,21 @@ class UsersViewSet(ModelCrudViewSet):
|
||||||
elif not valid_new_email:
|
elif not valid_new_email:
|
||||||
raise exc.WrongArguments(_("Not valid email"))
|
raise exc.WrongArguments(_("Not valid email"))
|
||||||
|
|
||||||
#We need to generate a token for the email
|
# We need to generate a token for the email
|
||||||
request.user.email_token = str(uuid.uuid1())
|
request.user.email_token = str(uuid.uuid1())
|
||||||
request.user.new_email = new_email
|
request.user.new_email = new_email
|
||||||
request.user.save(update_fields=["email_token", "new_email"])
|
request.user.save(update_fields=["email_token", "new_email"])
|
||||||
email = mail_builder.change_email(request.user.new_email, {"user": request.user,
|
email = mail_builder.change_email(
|
||||||
"lang": request.user.lang})
|
request.user.new_email,
|
||||||
|
{
|
||||||
|
"user": request.user,
|
||||||
|
"lang": request.user.lang
|
||||||
|
}
|
||||||
|
)
|
||||||
email.send()
|
email.send()
|
||||||
|
|
||||||
|
ret = super().partial_update(request, *args, **kwargs)
|
||||||
|
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
def destroy(self, request, pk=None):
|
def destroy(self, request, pk=None):
|
||||||
|
@ -165,16 +179,16 @@ class UsersViewSet(ModelCrudViewSet):
|
||||||
|
|
||||||
self.check_permissions(request, "change_password_from_recovery", None)
|
self.check_permissions(request, "change_password_from_recovery", None)
|
||||||
|
|
||||||
serializer = serializers.RecoverySerializer(data=request.DATA, many=False)
|
validator = validators.RecoveryValidator(data=request.DATA, many=False)
|
||||||
if not serializer.is_valid():
|
if not validator.is_valid():
|
||||||
raise exc.WrongArguments(_("Token is invalid"))
|
raise exc.WrongArguments(_("Token is invalid"))
|
||||||
|
|
||||||
try:
|
try:
|
||||||
user = models.User.objects.get(token=serializer.data["token"])
|
user = models.User.objects.get(token=validator.data["token"])
|
||||||
except models.User.DoesNotExist:
|
except models.User.DoesNotExist:
|
||||||
raise exc.WrongArguments(_("Token is invalid"))
|
raise exc.WrongArguments(_("Token is invalid"))
|
||||||
|
|
||||||
user.set_password(serializer.data["password"])
|
user.set_password(validator.data["password"])
|
||||||
user.token = None
|
user.token = None
|
||||||
user.save(update_fields=["password", "token"])
|
user.save(update_fields=["password", "token"])
|
||||||
|
|
||||||
|
@ -247,13 +261,13 @@ class UsersViewSet(ModelCrudViewSet):
|
||||||
"""
|
"""
|
||||||
Verify the email change to current logged user.
|
Verify the email change to current logged user.
|
||||||
"""
|
"""
|
||||||
serializer = serializers.ChangeEmailSerializer(data=request.DATA, many=False)
|
validator = validators.ChangeEmailValidator(data=request.DATA, many=False)
|
||||||
if not serializer.is_valid():
|
if not validator.is_valid():
|
||||||
raise exc.WrongArguments(_("Invalid, are you sure the token is correct and you "
|
raise exc.WrongArguments(_("Invalid, are you sure the token is correct and you "
|
||||||
"didn't use it before?"))
|
"didn't use it before?"))
|
||||||
|
|
||||||
try:
|
try:
|
||||||
user = models.User.objects.get(email_token=serializer.data["email_token"])
|
user = models.User.objects.get(email_token=validator.data["email_token"])
|
||||||
except models.User.DoesNotExist:
|
except models.User.DoesNotExist:
|
||||||
raise exc.WrongArguments(_("Invalid, are you sure the token is correct and you "
|
raise exc.WrongArguments(_("Invalid, are you sure the token is correct and you "
|
||||||
"didn't use it before?"))
|
"didn't use it before?"))
|
||||||
|
@ -280,13 +294,13 @@ class UsersViewSet(ModelCrudViewSet):
|
||||||
"""
|
"""
|
||||||
Cancel an account via token
|
Cancel an account via token
|
||||||
"""
|
"""
|
||||||
serializer = serializers.CancelAccountSerializer(data=request.DATA, many=False)
|
validator = validators.CancelAccountValidator(data=request.DATA, many=False)
|
||||||
if not serializer.is_valid():
|
if not validator.is_valid():
|
||||||
raise exc.WrongArguments(_("Invalid, are you sure the token is correct?"))
|
raise exc.WrongArguments(_("Invalid, are you sure the token is correct?"))
|
||||||
|
|
||||||
try:
|
try:
|
||||||
max_age_cancel_account = getattr(settings, "MAX_AGE_CANCEL_ACCOUNT", None)
|
max_age_cancel_account = getattr(settings, "MAX_AGE_CANCEL_ACCOUNT", None)
|
||||||
user = get_user_for_token(serializer.data["cancel_token"], "cancel_account",
|
user = get_user_for_token(validator.data["cancel_token"], "cancel_account",
|
||||||
max_age=max_age_cancel_account)
|
max_age=max_age_cancel_account)
|
||||||
|
|
||||||
except exc.NotAuthenticated:
|
except exc.NotAuthenticated:
|
||||||
|
@ -305,7 +319,7 @@ class UsersViewSet(ModelCrudViewSet):
|
||||||
|
|
||||||
self.object_list = user_filters.ContactsFilterBackend().filter_queryset(
|
self.object_list = user_filters.ContactsFilterBackend().filter_queryset(
|
||||||
user, request, self.get_queryset(), self).extra(
|
user, request, self.get_queryset(), self).extra(
|
||||||
select={"complete_user_name":"concat(full_name, username)"}).order_by("complete_user_name")
|
select={"complete_user_name": "concat(full_name, username)"}).order_by("complete_user_name")
|
||||||
|
|
||||||
page = self.paginate_queryset(self.object_list)
|
page = self.paginate_queryset(self.object_list)
|
||||||
if page is not None:
|
if page is not None:
|
||||||
|
@ -349,10 +363,10 @@ class UsersViewSet(ModelCrudViewSet):
|
||||||
for elem in elements:
|
for elem in elements:
|
||||||
if elem["type"] == "project":
|
if elem["type"] == "project":
|
||||||
# projects are liked objects
|
# projects are liked objects
|
||||||
response_data.append(serializers.LikedObjectSerializer(elem, **extra_args_liked).data )
|
response_data.append(serializers.LikedObjectSerializer(into_namedtuple(elem), **extra_args_liked).data)
|
||||||
else:
|
else:
|
||||||
# stories, tasks and issues are voted objects
|
# stories, tasks and issues are voted objects
|
||||||
response_data.append(serializers.VotedObjectSerializer(elem, **extra_args_voted).data )
|
response_data.append(serializers.VotedObjectSerializer(into_namedtuple(elem), **extra_args_voted).data)
|
||||||
|
|
||||||
return response.Ok(response_data)
|
return response.Ok(response_data)
|
||||||
|
|
||||||
|
@ -374,7 +388,7 @@ class UsersViewSet(ModelCrudViewSet):
|
||||||
"user_likes": services.get_liked_content_for_user(request.user),
|
"user_likes": services.get_liked_content_for_user(request.user),
|
||||||
}
|
}
|
||||||
|
|
||||||
response_data = [serializers.LikedObjectSerializer(elem, **extra_args).data for elem in elements]
|
response_data = [serializers.LikedObjectSerializer(into_namedtuple(elem), **extra_args).data for elem in elements]
|
||||||
|
|
||||||
return response.Ok(response_data)
|
return response.Ok(response_data)
|
||||||
|
|
||||||
|
@ -397,17 +411,18 @@ class UsersViewSet(ModelCrudViewSet):
|
||||||
"user_votes": services.get_voted_content_for_user(request.user),
|
"user_votes": services.get_voted_content_for_user(request.user),
|
||||||
}
|
}
|
||||||
|
|
||||||
response_data = [serializers.VotedObjectSerializer(elem, **extra_args).data for elem in elements]
|
response_data = [serializers.VotedObjectSerializer(into_namedtuple(elem), **extra_args).data for elem in elements]
|
||||||
|
|
||||||
return response.Ok(response_data)
|
return response.Ok(response_data)
|
||||||
|
|
||||||
######################################################
|
|
||||||
## Role
|
|
||||||
######################################################
|
|
||||||
|
|
||||||
|
######################################################
|
||||||
|
# Role
|
||||||
|
######################################################
|
||||||
class RolesViewSet(BlockedByProjectMixin, ModelCrudViewSet):
|
class RolesViewSet(BlockedByProjectMixin, ModelCrudViewSet):
|
||||||
model = models.Role
|
model = models.Role
|
||||||
serializer_class = serializers.RoleSerializer
|
serializer_class = serializers.RoleSerializer
|
||||||
|
validator_class = validators.RoleValidator
|
||||||
permission_classes = (permissions.RolesPermission, )
|
permission_classes = (permissions.RolesPermission, )
|
||||||
filter_backends = (filters.CanViewProjectFilterBackend,)
|
filter_backends = (filters.CanViewProjectFilterBackend,)
|
||||||
filter_fields = ('project',)
|
filter_fields = ('project',)
|
||||||
|
|
|
@ -22,7 +22,7 @@ from django.core.exceptions import ValidationError
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
from taiga.base.api import serializers
|
from taiga.base.api import serializers
|
||||||
from taiga.base.fields import PgArrayField, Field, MethodField
|
from taiga.base.fields import PgArrayField, Field, MethodField, I18NField
|
||||||
|
|
||||||
from taiga.base.utils.thumbnails import get_thumbnail_url
|
from taiga.base.utils.thumbnails import get_thumbnail_url
|
||||||
|
|
||||||
|
@ -40,47 +40,28 @@ import re
|
||||||
# User
|
# User
|
||||||
######################################################
|
######################################################
|
||||||
|
|
||||||
class ContactProjectDetailSerializer(serializers.ModelSerializer):
|
class ContactProjectDetailSerializer(serializers.LightSerializer):
|
||||||
class Meta:
|
id = Field()
|
||||||
model = Project
|
slug = Field()
|
||||||
fields = ("id", "slug", "name")
|
name = Field()
|
||||||
|
|
||||||
|
|
||||||
class UserSerializer(serializers.ModelSerializer):
|
class UserSerializer(serializers.LightSerializer):
|
||||||
full_name_display = serializers.SerializerMethodField("get_full_name_display")
|
id = Field()
|
||||||
photo = serializers.SerializerMethodField("get_photo")
|
username = Field()
|
||||||
big_photo = serializers.SerializerMethodField("get_big_photo")
|
full_name = Field()
|
||||||
gravatar_url = serializers.SerializerMethodField("get_gravatar_url")
|
full_name_display = MethodField()
|
||||||
roles = serializers.SerializerMethodField("get_roles")
|
color = Field()
|
||||||
projects_with_me = serializers.SerializerMethodField("get_projects_with_me")
|
bio = Field()
|
||||||
|
lang = Field()
|
||||||
class Meta:
|
theme = Field()
|
||||||
model = User
|
timezone = Field()
|
||||||
# IMPORTANT: Maintain the UserAdminSerializer Meta up to date
|
is_active = Field()
|
||||||
# with this info (including there the email)
|
photo = MethodField()
|
||||||
fields = ("id", "username", "full_name", "full_name_display",
|
big_photo = MethodField()
|
||||||
"color", "bio", "lang", "theme", "timezone", "is_active",
|
gravatar_url = MethodField()
|
||||||
"photo", "big_photo", "roles", "projects_with_me",
|
roles = MethodField()
|
||||||
"gravatar_url")
|
projects_with_me = MethodField()
|
||||||
read_only_fields = ("id",)
|
|
||||||
|
|
||||||
def validate_username(self, attrs, source):
|
|
||||||
value = attrs[source]
|
|
||||||
validator = validators.RegexValidator(re.compile('^[\w.-]+$'), _("invalid username"),
|
|
||||||
_("invalid"))
|
|
||||||
|
|
||||||
try:
|
|
||||||
validator(value)
|
|
||||||
except ValidationError:
|
|
||||||
raise serializers.ValidationError(_("Required. 255 characters or fewer. Letters, "
|
|
||||||
"numbers and /./-/_ characters'"))
|
|
||||||
|
|
||||||
if (self.object and
|
|
||||||
self.object.username != value and
|
|
||||||
User.objects.filter(username=value).exists()):
|
|
||||||
raise serializers.ValidationError(_("Invalid username. Try with a different one."))
|
|
||||||
|
|
||||||
return attrs
|
|
||||||
|
|
||||||
def get_full_name_display(self, obj):
|
def get_full_name_display(self, obj):
|
||||||
return obj.get_full_name() if obj else ""
|
return obj.get_full_name() if obj else ""
|
||||||
|
@ -113,24 +94,13 @@ class UserSerializer(serializers.ModelSerializer):
|
||||||
|
|
||||||
|
|
||||||
class UserAdminSerializer(UserSerializer):
|
class UserAdminSerializer(UserSerializer):
|
||||||
total_private_projects = serializers.SerializerMethodField("get_total_private_projects")
|
total_private_projects = MethodField()
|
||||||
total_public_projects = serializers.SerializerMethodField("get_total_public_projects")
|
total_public_projects = MethodField()
|
||||||
|
email = Field()
|
||||||
class Meta:
|
max_private_projects = Field()
|
||||||
model = User
|
max_public_projects = Field()
|
||||||
# IMPORTANT: Maintain the UserSerializer Meta up to date
|
max_memberships_private_projects = Field()
|
||||||
# with this info (including here the email)
|
max_memberships_public_projects = Field()
|
||||||
fields = ("id", "username", "full_name", "full_name_display", "email",
|
|
||||||
"color", "bio", "lang", "theme", "timezone", "is_active", "photo",
|
|
||||||
"big_photo", "gravatar_url",
|
|
||||||
"max_private_projects", "max_public_projects",
|
|
||||||
"max_memberships_private_projects", "max_memberships_public_projects",
|
|
||||||
"total_private_projects", "total_public_projects")
|
|
||||||
|
|
||||||
read_only_fields = ("id", "email",
|
|
||||||
"max_private_projects", "max_public_projects",
|
|
||||||
"max_memberships_private_projects",
|
|
||||||
"max_memberships_public_projects")
|
|
||||||
|
|
||||||
def get_total_private_projects(self, user):
|
def get_total_private_projects(self, user):
|
||||||
return user.owned_projects.filter(is_private=True).count()
|
return user.owned_projects.filter(is_private=True).count()
|
||||||
|
@ -163,75 +133,63 @@ class UserBasicInfoSerializer(serializers.LightSerializer):
|
||||||
return super().to_value(instance)
|
return super().to_value(instance)
|
||||||
|
|
||||||
|
|
||||||
class RecoverySerializer(serializers.Serializer):
|
|
||||||
token = serializers.CharField(max_length=200)
|
|
||||||
password = serializers.CharField(min_length=6)
|
|
||||||
|
|
||||||
|
|
||||||
class ChangeEmailSerializer(serializers.Serializer):
|
|
||||||
email_token = serializers.CharField(max_length=200)
|
|
||||||
|
|
||||||
|
|
||||||
class CancelAccountSerializer(serializers.Serializer):
|
|
||||||
cancel_token = serializers.CharField(max_length=200)
|
|
||||||
|
|
||||||
|
|
||||||
######################################################
|
######################################################
|
||||||
# Role
|
# Role
|
||||||
######################################################
|
######################################################
|
||||||
|
|
||||||
class RoleSerializer(serializers.ModelSerializer):
|
class RoleSerializer(serializers.LightSerializer):
|
||||||
members_count = serializers.SerializerMethodField("get_members_count")
|
id = Field()
|
||||||
|
name = Field()
|
||||||
|
computable = Field()
|
||||||
|
project = Field(attr="project_id")
|
||||||
|
order = Field()
|
||||||
|
members_count = MethodField()
|
||||||
permissions = PgArrayField(required=False)
|
permissions = PgArrayField(required=False)
|
||||||
|
|
||||||
class Meta:
|
|
||||||
model = Role
|
|
||||||
fields = ('id', 'name', 'permissions', 'computable', 'project', 'order', 'members_count')
|
|
||||||
i18n_fields = ("name",)
|
|
||||||
|
|
||||||
def get_members_count(self, obj):
|
def get_members_count(self, obj):
|
||||||
return obj.memberships.count()
|
return obj.memberships.count()
|
||||||
|
|
||||||
|
|
||||||
class ProjectRoleSerializer(serializers.ModelSerializer):
|
class ProjectRoleSerializer(serializers.LightSerializer):
|
||||||
class Meta:
|
id = Field()
|
||||||
model = Role
|
name = I18NField()
|
||||||
fields = ('id', 'name', 'slug', 'order', 'computable')
|
slug = Field()
|
||||||
i18n_fields = ("name",)
|
order = Field()
|
||||||
|
computable = Field()
|
||||||
|
|
||||||
|
|
||||||
######################################################
|
######################################################
|
||||||
# Like
|
# Like
|
||||||
######################################################
|
######################################################
|
||||||
|
|
||||||
class HighLightedContentSerializer(serializers.Serializer):
|
class HighLightedContentSerializer(serializers.LightSerializer):
|
||||||
type = serializers.CharField()
|
type = Field()
|
||||||
id = serializers.IntegerField()
|
id = Field()
|
||||||
ref = serializers.IntegerField()
|
ref = Field()
|
||||||
slug = serializers.CharField()
|
slug = Field()
|
||||||
name = serializers.CharField()
|
name = Field()
|
||||||
subject = serializers.CharField()
|
subject = Field()
|
||||||
description = serializers.SerializerMethodField("get_description")
|
description = MethodField()
|
||||||
assigned_to = serializers.IntegerField()
|
assigned_to = Field()
|
||||||
status = serializers.CharField()
|
status = Field()
|
||||||
status_color = serializers.CharField()
|
status_color = Field()
|
||||||
tags_colors = serializers.SerializerMethodField("get_tags_color")
|
tags_colors = MethodField()
|
||||||
created_date = serializers.DateTimeField()
|
created_date = Field()
|
||||||
is_private = serializers.SerializerMethodField("get_is_private")
|
is_private = MethodField()
|
||||||
logo_small_url = serializers.SerializerMethodField("get_logo_small_url")
|
logo_small_url = MethodField()
|
||||||
|
|
||||||
project = serializers.SerializerMethodField("get_project")
|
project = MethodField()
|
||||||
project_name = serializers.SerializerMethodField("get_project_name")
|
project_name = MethodField()
|
||||||
project_slug = serializers.SerializerMethodField("get_project_slug")
|
project_slug = MethodField()
|
||||||
project_is_private = serializers.SerializerMethodField("get_project_is_private")
|
project_is_private = MethodField()
|
||||||
project_blocked_code = serializers.CharField()
|
project_blocked_code = Field()
|
||||||
|
|
||||||
assigned_to_username = serializers.CharField()
|
assigned_to_username = Field()
|
||||||
assigned_to_full_name = serializers.CharField()
|
assigned_to_full_name = Field()
|
||||||
assigned_to_photo = serializers.SerializerMethodField("get_photo")
|
assigned_to_photo = MethodField()
|
||||||
|
|
||||||
is_watcher = serializers.SerializerMethodField("get_is_watcher")
|
is_watcher = MethodField()
|
||||||
total_watchers = serializers.IntegerField()
|
total_watchers = Field()
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
# Don't pass the extra ids args up to the superclass
|
# Don't pass the extra ids args up to the superclass
|
||||||
|
@ -241,18 +199,18 @@ class HighLightedContentSerializer(serializers.Serializer):
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
def _none_if_project(self, obj, property):
|
def _none_if_project(self, obj, property):
|
||||||
type = obj.get("type", "")
|
type = getattr(obj, "type", "")
|
||||||
if type == "project":
|
if type == "project":
|
||||||
return None
|
return None
|
||||||
|
|
||||||
return obj.get(property)
|
return getattr(obj, property)
|
||||||
|
|
||||||
def _none_if_not_project(self, obj, property):
|
def _none_if_not_project(self, obj, property):
|
||||||
type = obj.get("type", "")
|
type = getattr(obj, "type", "")
|
||||||
if type != "project":
|
if type != "project":
|
||||||
return None
|
return None
|
||||||
|
|
||||||
return obj.get(property)
|
return getattr(obj, property)
|
||||||
|
|
||||||
def get_project(self, obj):
|
def get_project(self, obj):
|
||||||
return self._none_if_project(obj, "project")
|
return self._none_if_project(obj, "project")
|
||||||
|
@ -278,29 +236,29 @@ class HighLightedContentSerializer(serializers.Serializer):
|
||||||
return get_thumbnail_url(logo, settings.THN_LOGO_SMALL)
|
return get_thumbnail_url(logo, settings.THN_LOGO_SMALL)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def get_photo(self, obj):
|
def get_assigned_to_photo(self, obj):
|
||||||
type = obj.get("type", "")
|
type = getattr(obj, "type", "")
|
||||||
if type == "project":
|
if type == "project":
|
||||||
return None
|
return None
|
||||||
|
|
||||||
UserData = namedtuple("UserData", ["photo", "email"])
|
UserData = namedtuple("UserData", ["photo", "email"])
|
||||||
user_data = UserData(photo=obj["assigned_to_photo"], email=obj.get("assigned_to_email") or "")
|
user_data = UserData(photo=obj.assigned_to_photo, email=obj.assigned_to_email or "")
|
||||||
return get_photo_or_gravatar_url(user_data)
|
return get_photo_or_gravatar_url(user_data)
|
||||||
|
|
||||||
def get_tags_color(self, obj):
|
def get_tags_colors(self, obj):
|
||||||
tags = obj.get("tags", [])
|
tags = getattr(obj, "tags", [])
|
||||||
tags = tags if tags is not None else []
|
tags = tags if tags is not None else []
|
||||||
tags_colors = obj.get("tags_colors", [])
|
tags_colors = getattr(obj, "tags_colors", [])
|
||||||
tags_colors = tags_colors if tags_colors is not None else []
|
tags_colors = tags_colors if tags_colors is not None else []
|
||||||
return [{"name": tc[0], "color": tc[1]} for tc in tags_colors if tc[0] in tags]
|
return [{"name": tc[0], "color": tc[1]} for tc in tags_colors if tc[0] in tags]
|
||||||
|
|
||||||
def get_is_watcher(self, obj):
|
def get_is_watcher(self, obj):
|
||||||
return obj["id"] in self.user_watching.get(obj["type"], [])
|
return obj.id in self.user_watching.get(obj.type, [])
|
||||||
|
|
||||||
|
|
||||||
class LikedObjectSerializer(HighLightedContentSerializer):
|
class LikedObjectSerializer(HighLightedContentSerializer):
|
||||||
is_fan = serializers.SerializerMethodField("get_is_fan")
|
is_fan = MethodField()
|
||||||
total_fans = serializers.IntegerField()
|
total_fans = Field()
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
# Don't pass the extra ids args up to the superclass
|
# Don't pass the extra ids args up to the superclass
|
||||||
|
@ -310,12 +268,12 @@ class LikedObjectSerializer(HighLightedContentSerializer):
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
def get_is_fan(self, obj):
|
def get_is_fan(self, obj):
|
||||||
return obj["id"] in self.user_likes.get(obj["type"], [])
|
return obj.id in self.user_likes.get(obj.type, [])
|
||||||
|
|
||||||
|
|
||||||
class VotedObjectSerializer(HighLightedContentSerializer):
|
class VotedObjectSerializer(HighLightedContentSerializer):
|
||||||
is_voter = serializers.SerializerMethodField("get_is_voter")
|
is_voter = MethodField()
|
||||||
total_voters = serializers.IntegerField()
|
total_voters = Field()
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
# Don't pass the extra ids args up to the superclass
|
# Don't pass the extra ids args up to the superclass
|
||||||
|
@ -325,4 +283,4 @@ class VotedObjectSerializer(HighLightedContentSerializer):
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
def get_is_voter(self, obj):
|
def get_is_voter(self, obj):
|
||||||
return obj["id"] in self.user_votes.get(obj["type"], [])
|
return obj.id in self.user_votes.get(obj.type, [])
|
||||||
|
|
|
@ -3,7 +3,6 @@
|
||||||
# Copyright (C) 2014-2016 Jesús Espino <jespinog@gmail.com>
|
# Copyright (C) 2014-2016 Jesús Espino <jespinog@gmail.com>
|
||||||
# Copyright (C) 2014-2016 David Barragán <bameda@dbarragan.com>
|
# Copyright (C) 2014-2016 David Barragán <bameda@dbarragan.com>
|
||||||
# Copyright (C) 2014-2016 Alejandro Alonso <alejandro.alonso@kaleidos.net>
|
# Copyright (C) 2014-2016 Alejandro Alonso <alejandro.alonso@kaleidos.net>
|
||||||
# Copyright (C) 2014-2016 Anler Hernández <hello@anler.me>
|
|
||||||
# This program is free software: you can redistribute it and/or modify
|
# This program is free software: you can redistribute it and/or modify
|
||||||
# it under the terms of the GNU Affero General Public License as
|
# it under the terms of the GNU Affero General Public License as
|
||||||
# published by the Free Software Foundation, either version 3 of the
|
# published by the Free Software Foundation, either version 3 of the
|
||||||
|
@ -17,17 +16,92 @@
|
||||||
# You should have received a copy of the GNU Affero General Public License
|
# 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/>.
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
from django.utils.translation import ugettext as _
|
from django.core import validators as core_validators
|
||||||
|
from django.core.exceptions import ValidationError
|
||||||
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
from taiga.base.api import serializers
|
from taiga.base.api import serializers
|
||||||
|
from taiga.base.api import validators
|
||||||
|
from taiga.base.fields import PgArrayField, Field
|
||||||
|
|
||||||
from . import models
|
from .models import User, Role
|
||||||
|
|
||||||
|
import re
|
||||||
|
|
||||||
|
|
||||||
class RoleExistsValidator:
|
class RoleExistsValidator:
|
||||||
def validate_role_id(self, attrs, source):
|
def validate_role_id(self, attrs, source):
|
||||||
value = attrs[source]
|
value = attrs[source]
|
||||||
if not models.Role.objects.filter(pk=value).exists():
|
if not Role.objects.filter(pk=value).exists():
|
||||||
msg = _("There's no role with that id")
|
msg = _("There's no role with that id")
|
||||||
raise serializers.ValidationError(msg)
|
raise serializers.ValidationError(msg)
|
||||||
return attrs
|
return attrs
|
||||||
|
|
||||||
|
|
||||||
|
######################################################
|
||||||
|
# User
|
||||||
|
######################################################
|
||||||
|
class UserValidator(validators.ModelValidator):
|
||||||
|
class Meta:
|
||||||
|
model = User
|
||||||
|
fields = ("username", "full_name", "color", "bio", "lang",
|
||||||
|
"theme", "timezone", "is_active")
|
||||||
|
|
||||||
|
def validate_username(self, attrs, source):
|
||||||
|
value = attrs[source]
|
||||||
|
validator = core_validators.RegexValidator(re.compile('^[\w.-]+$'), _("invalid username"),
|
||||||
|
_("invalid"))
|
||||||
|
|
||||||
|
try:
|
||||||
|
validator(value)
|
||||||
|
except ValidationError:
|
||||||
|
raise validators.ValidationError(_("Required. 255 characters or fewer. Letters, "
|
||||||
|
"numbers and /./-/_ characters'"))
|
||||||
|
|
||||||
|
if (self.object and
|
||||||
|
self.object.username != value and
|
||||||
|
User.objects.filter(username=value).exists()):
|
||||||
|
raise validators.ValidationError(_("Invalid username. Try with a different one."))
|
||||||
|
|
||||||
|
return attrs
|
||||||
|
|
||||||
|
|
||||||
|
class UserAdminValidator(UserValidator):
|
||||||
|
class Meta:
|
||||||
|
model = User
|
||||||
|
# IMPORTANT: Maintain the UserSerializer Meta up to date
|
||||||
|
# with this info (including here the email)
|
||||||
|
fields = ("username", "full_name", "color", "bio", "lang",
|
||||||
|
"theme", "timezone", "is_active", "email")
|
||||||
|
|
||||||
|
|
||||||
|
class RecoveryValidator(validators.Validator):
|
||||||
|
token = serializers.CharField(max_length=200)
|
||||||
|
password = serializers.CharField(min_length=6)
|
||||||
|
|
||||||
|
|
||||||
|
class ChangeEmailValidator(validators.Validator):
|
||||||
|
email_token = serializers.CharField(max_length=200)
|
||||||
|
|
||||||
|
|
||||||
|
class CancelAccountValidator(validators.Validator):
|
||||||
|
cancel_token = serializers.CharField(max_length=200)
|
||||||
|
|
||||||
|
|
||||||
|
######################################################
|
||||||
|
# Role
|
||||||
|
######################################################
|
||||||
|
|
||||||
|
class RoleValidator(validators.ModelValidator):
|
||||||
|
permissions = PgArrayField(required=False)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = Role
|
||||||
|
fields = ('id', 'name', 'permissions', 'computable', 'project', 'order')
|
||||||
|
i18n_fields = ("name",)
|
||||||
|
|
||||||
|
|
||||||
|
class ProjectRoleValidator(validators.ModelValidator):
|
||||||
|
class Meta:
|
||||||
|
model = Role
|
||||||
|
fields = ('id', 'name', 'slug', 'order', 'computable')
|
||||||
|
|
|
@ -30,6 +30,7 @@ from ..utils import DUMMY_BMP_DATA
|
||||||
|
|
||||||
from taiga.base.utils import json
|
from taiga.base.utils import json
|
||||||
from taiga.base.utils.thumbnails import get_thumbnail_url
|
from taiga.base.utils.thumbnails import get_thumbnail_url
|
||||||
|
from taiga.base.utils.dicts import into_namedtuple
|
||||||
from taiga.users import models
|
from taiga.users import models
|
||||||
from taiga.users.serializers import LikedObjectSerializer, VotedObjectSerializer
|
from taiga.users.serializers import LikedObjectSerializer, VotedObjectSerializer
|
||||||
from taiga.auth.tokens import get_token_for_user
|
from taiga.auth.tokens import get_token_for_user
|
||||||
|
@ -505,7 +506,7 @@ def test_get_watched_list_valid_info_for_project():
|
||||||
|
|
||||||
raw_project_watch_info = get_watched_list(fav_user, viewer_user)[0]
|
raw_project_watch_info = get_watched_list(fav_user, viewer_user)[0]
|
||||||
|
|
||||||
project_watch_info = LikedObjectSerializer(raw_project_watch_info).data
|
project_watch_info = LikedObjectSerializer(into_namedtuple(raw_project_watch_info)).data
|
||||||
|
|
||||||
assert project_watch_info["type"] == "project"
|
assert project_watch_info["type"] == "project"
|
||||||
assert project_watch_info["id"] == project.id
|
assert project_watch_info["id"] == project.id
|
||||||
|
@ -559,7 +560,7 @@ def test_get_liked_list_valid_info():
|
||||||
project.refresh_totals()
|
project.refresh_totals()
|
||||||
|
|
||||||
raw_project_like_info = get_liked_list(fan_user, viewer_user)[0]
|
raw_project_like_info = get_liked_list(fan_user, viewer_user)[0]
|
||||||
project_like_info = LikedObjectSerializer(raw_project_like_info).data
|
project_like_info = LikedObjectSerializer(into_namedtuple(raw_project_like_info)).data
|
||||||
|
|
||||||
assert project_like_info["type"] == "project"
|
assert project_like_info["type"] == "project"
|
||||||
assert project_like_info["id"] == project.id
|
assert project_like_info["id"] == project.id
|
||||||
|
@ -609,7 +610,7 @@ def test_get_watched_list_valid_info_for_not_project_types():
|
||||||
|
|
||||||
instance.add_watcher(fav_user)
|
instance.add_watcher(fav_user)
|
||||||
raw_instance_watch_info = get_watched_list(fav_user, viewer_user, type=object_type)[0]
|
raw_instance_watch_info = get_watched_list(fav_user, viewer_user, type=object_type)[0]
|
||||||
instance_watch_info = VotedObjectSerializer(raw_instance_watch_info).data
|
instance_watch_info = VotedObjectSerializer(into_namedtuple(raw_instance_watch_info)).data
|
||||||
|
|
||||||
assert instance_watch_info["type"] == object_type
|
assert instance_watch_info["type"] == object_type
|
||||||
assert instance_watch_info["id"] == instance.id
|
assert instance_watch_info["id"] == instance.id
|
||||||
|
@ -666,7 +667,7 @@ def test_get_voted_list_valid_info():
|
||||||
f.VotesFactory(content_type=content_type, object_id=instance.id, count=3)
|
f.VotesFactory(content_type=content_type, object_id=instance.id, count=3)
|
||||||
|
|
||||||
raw_instance_vote_info = get_voted_list(fav_user, viewer_user, type=object_type)[0]
|
raw_instance_vote_info = get_voted_list(fav_user, viewer_user, type=object_type)[0]
|
||||||
instance_vote_info = VotedObjectSerializer(raw_instance_vote_info).data
|
instance_vote_info = VotedObjectSerializer(into_namedtuple(raw_instance_vote_info)).data
|
||||||
|
|
||||||
assert instance_vote_info["type"] == object_type
|
assert instance_vote_info["type"] == object_type
|
||||||
assert instance_vote_info["id"] == instance.id
|
assert instance_vote_info["id"] == instance.id
|
||||||
|
|
Loading…
Reference in New Issue