Merge pull request #68 from taigaio/django1.7-step2-bugs
Fix wrong assignation of invited_by field on invitation is created.remotes/origin/enhancement/email-actions
commit
6928c39281
|
@ -130,7 +130,8 @@ def store_membership(project, membership):
|
|||
serialized.object._importing = True
|
||||
if not serialized.object.token:
|
||||
serialized.object.token = str(uuid.uuid1())
|
||||
serialized.object.user = find_invited_user(serialized.object, default=serialized.object.user)
|
||||
serialized.object.user = find_invited_user(serialized.object.email,
|
||||
default=serialized.object.user)
|
||||
serialized.save()
|
||||
return serialized
|
||||
|
||||
|
|
|
@ -185,52 +185,29 @@ class MembershipViewSet(ModelCrudViewSet):
|
|||
filter_backends = (filters.CanViewProjectFilterBackend,)
|
||||
filter_fields = ("project", "role")
|
||||
|
||||
def create(self, request, *args, **kwargs):
|
||||
data = request.DATA.copy()
|
||||
data.update({"invited_by_id": request.user.id})
|
||||
serializer = self.get_serializer(data=data, files=request.FILES)
|
||||
|
||||
if serializer.is_valid():
|
||||
project_id = serializer.data["project"]
|
||||
project = get_object_or_404(models.Project, id=project_id)
|
||||
|
||||
self.check_permissions(request, 'create', project)
|
||||
|
||||
qs = self.model.objects.filter(Q(project_id=project_id,
|
||||
user__email=serializer.data["email"]) |
|
||||
Q(project_id=project_id,
|
||||
email=serializer.data["email"]))
|
||||
if qs.count() > 0:
|
||||
raise exc.WrongArguments(_("Email address is already taken."))
|
||||
|
||||
self.pre_save(serializer.object)
|
||||
self.object = serializer.save(force_insert=True)
|
||||
self.post_save(self.object, created=True)
|
||||
headers = self.get_success_headers(serializer.data)
|
||||
return Response(serializer.data, status=status.HTTP_201_CREATED,
|
||||
headers=headers)
|
||||
|
||||
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
@list_route(methods=["POST"])
|
||||
def bulk_create(self, request, **kwargs):
|
||||
serializer = serializers.MembersBulkSerializer(data=request.DATA)
|
||||
if serializer.is_valid():
|
||||
data = serializer.data
|
||||
project = models.Project.objects.get(id=data["project_id"])
|
||||
self.check_permissions(request, 'bulk_create', project)
|
||||
try:
|
||||
members = services.create_members_in_bulk(
|
||||
data["bulk_memberships"], project=project, callback=self.post_save,
|
||||
precall=self.pre_save)
|
||||
except ValidationError as err:
|
||||
return Response(err.message_dict, status=status.HTTP_400_BAD_REQUEST)
|
||||
if not serializer.is_valid():
|
||||
return response.BadRequest(serializer.errors)
|
||||
|
||||
members_serialized = self.serializer_class(members, many=True)
|
||||
data = serializer.data
|
||||
project = models.Project.objects.get(id=data["project_id"])
|
||||
self.check_permissions(request, 'bulk_create', project)
|
||||
|
||||
return response.Ok(data=members_serialized.data)
|
||||
# TODO: this should be moved to main exception handler instead
|
||||
# of handling explicit exception catchin here.
|
||||
|
||||
return response.BadRequest(serializer.errors)
|
||||
try:
|
||||
members = services.create_members_in_bulk(data["bulk_memberships"],
|
||||
project=project,
|
||||
callback=self.post_save,
|
||||
precall=self.pre_save)
|
||||
except ValidationError as err:
|
||||
return response.BadRequest(err.message_dict)
|
||||
|
||||
members_serialized = self.serializer_class(members, many=True)
|
||||
return response.Ok(data=members_serialized.data)
|
||||
|
||||
@detail_route(methods=["POST"])
|
||||
def resend_invitation(self, request, **kwargs):
|
||||
|
@ -241,14 +218,13 @@ class MembershipViewSet(ModelCrudViewSet):
|
|||
services.send_invitation(invitation=invitation)
|
||||
return Response(status=status.HTTP_204_NO_CONTENT)
|
||||
|
||||
def pre_save(self, object):
|
||||
# Only assign new token if a current token value is empty.
|
||||
if not object.token:
|
||||
object.token = str(uuid.uuid1())
|
||||
def pre_save(self, obj):
|
||||
if not obj.token:
|
||||
obj.token = str(uuid.uuid1())
|
||||
|
||||
object.user = services.find_invited_user(object, default=object.user)
|
||||
|
||||
super().pre_save(object)
|
||||
obj.invited_by = self.request.user
|
||||
obj.user = services.find_invited_user(obj.email, default=obj.user)
|
||||
super().pre_save(obj)
|
||||
|
||||
def post_save(self, object, created=False):
|
||||
super().post_save(object, created=created)
|
||||
|
|
|
@ -0,0 +1,35 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import models, migrations
|
||||
from django.conf import settings
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
('projects', '0002_auto_20140903_0920'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RenameField(
|
||||
model_name='membership',
|
||||
old_name='invited_by_id',
|
||||
new_name='invited_by_id_old',
|
||||
),
|
||||
|
||||
migrations.AddField(
|
||||
model_name='membership',
|
||||
name='invited_by',
|
||||
field=models.ForeignKey(null=True, to=settings.AUTH_USER_MODEL, blank=True, related_name='ihaveinvited+'),
|
||||
preserve_default=True,
|
||||
),
|
||||
|
||||
migrations.RunSQL("UPDATE projects_membership SET invited_by_id = invited_by_id_old"),
|
||||
|
||||
migrations.RemoveField(
|
||||
model_name='membership',
|
||||
name='invited_by_id_old',
|
||||
),
|
||||
|
||||
]
|
|
@ -66,7 +66,9 @@ class Membership(models.Model):
|
|||
verbose_name=_("creado el"))
|
||||
token = models.CharField(max_length=60, blank=True, null=True, default=None,
|
||||
verbose_name=_("token"))
|
||||
invited_by_id = models.IntegerField(null=True, blank=True)
|
||||
|
||||
invited_by = models.ForeignKey(settings.AUTH_USER_MODEL, related_name="ihaveinvited+",
|
||||
null=True, blank=True)
|
||||
|
||||
def clean(self):
|
||||
# TODO: Review and do it more robust
|
||||
|
@ -130,7 +132,8 @@ class Project(ProjectDefaults, TaggedMixin, models.Model):
|
|||
owner = models.ForeignKey(settings.AUTH_USER_MODEL, null=False, blank=False,
|
||||
related_name="owned_projects", verbose_name=_("owner"))
|
||||
members = models.ManyToManyField(settings.AUTH_USER_MODEL, related_name="projects",
|
||||
through="Membership", verbose_name=_("members"))
|
||||
through="Membership", verbose_name=_("members"),
|
||||
through_fields=("project", "user"))
|
||||
total_milestones = models.IntegerField(default=0, null=True, blank=True,
|
||||
verbose_name=_("total of milestones"))
|
||||
total_story_points = models.FloatField(default=0, verbose_name=_("total story points"))
|
||||
|
|
|
@ -15,8 +15,11 @@
|
|||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from os import path
|
||||
from rest_framework import serializers
|
||||
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.db.models import Q
|
||||
|
||||
from rest_framework import serializers
|
||||
|
||||
from taiga.base.serializers import JsonField, PgArrayField, ModelSerializer, TagsColorsField
|
||||
from taiga.users.models import Role, User
|
||||
|
@ -97,9 +100,9 @@ class MembershipSerializer(ModelSerializer):
|
|||
email = serializers.EmailField(required=True)
|
||||
color = serializers.CharField(source='user.color', required=False, read_only=True)
|
||||
photo = serializers.SerializerMethodField("get_photo")
|
||||
invited_by = serializers.SerializerMethodField("get_invited_by")
|
||||
project_name = serializers.SerializerMethodField("get_project_name")
|
||||
project_slug = serializers.SerializerMethodField("get_project_slug")
|
||||
invited_by = UserSerializer(read_only=True)
|
||||
|
||||
class Meta:
|
||||
model = models.Membership
|
||||
|
@ -115,13 +118,24 @@ class MembershipSerializer(ModelSerializer):
|
|||
def get_project_slug(self, obj):
|
||||
return obj.project.slug if obj and obj.project else ""
|
||||
|
||||
def get_invited_by(self, membership):
|
||||
try:
|
||||
queryset = User.objects.get(pk=membership.invited_by_id)
|
||||
except User.DoesNotExist:
|
||||
return None
|
||||
else:
|
||||
return UserSerializer(queryset).data
|
||||
def validate_email(self, attrs, source):
|
||||
project = attrs["project"]
|
||||
email = attrs[source]
|
||||
|
||||
qs = models.Membership.objects.all()
|
||||
|
||||
# If self.object is not None, the serializer is in update
|
||||
# mode, and for it, it should exclude self.
|
||||
if self.object:
|
||||
qs = qs.exclude(pk=self.object.pk)
|
||||
|
||||
qs = qs.filter(Q(project_id=project.id, user__email=email) |
|
||||
Q(project_id=project.id, email=email))
|
||||
|
||||
if qs.count() > 0:
|
||||
raise serializers.ValidationError(_("Email address is already taken"))
|
||||
|
||||
return attrs
|
||||
|
||||
|
||||
class ProjectMembershipSerializer(ModelSerializer):
|
||||
|
|
|
@ -1,3 +1,6 @@
|
|||
from django.apps import apps
|
||||
from django.conf import settings
|
||||
|
||||
from djmail.template_mail import MagicMailBuilder
|
||||
|
||||
|
||||
|
@ -12,15 +15,20 @@ def send_invitation(invitation):
|
|||
email.send()
|
||||
|
||||
|
||||
def find_invited_user(invitation, default=None):
|
||||
def find_invited_user(email, default=None):
|
||||
"""Check if the invited user is already a registered.
|
||||
|
||||
:param invitation: Invitation object.
|
||||
:param default: Default object to return if user is not found.
|
||||
|
||||
TODO: only used by importer/exporter and should be moved here
|
||||
|
||||
:return: The user if it's found, othwerwise return `default`.
|
||||
"""
|
||||
|
||||
User = apps.get_model(settings.AUTH_USER_MODEL)
|
||||
|
||||
try:
|
||||
return type(invitation).user.get_queryset().filter(email=invitation.email).all()[0]
|
||||
except IndexError:
|
||||
return User.objects.get(email=email)
|
||||
except User.DoesNotExist:
|
||||
return default
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import models, migrations
|
||||
import django.core.validators
|
||||
import re
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('users', '0003_auto_20140903_0925'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='user',
|
||||
name='email',
|
||||
field=models.EmailField(blank=True, unique=True, max_length=255, verbose_name='email address'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='user',
|
||||
name='username',
|
||||
field=models.CharField(validators=[django.core.validators.RegexValidator(re.compile('^[\\w.-]+$', 32), 'Enter a valid username.', 'invalid')], max_length=255, unique=True, help_text='Required. 30 characters or fewer. Letters, numbers and /./-/_ characters', verbose_name='username'),
|
||||
),
|
||||
]
|
|
@ -70,13 +70,13 @@ class PermissionsMixin(models.Model):
|
|||
|
||||
|
||||
class User(AbstractBaseUser, PermissionsMixin):
|
||||
username = models.CharField(_('username'), max_length=30, unique=True,
|
||||
username = models.CharField(_('username'), max_length=255, unique=True,
|
||||
help_text=_('Required. 30 characters or fewer. Letters, numbers and '
|
||||
'/./-/_ characters'),
|
||||
validators=[
|
||||
validators.RegexValidator(re.compile('^[\w.-]+$'), _('Enter a valid username.'), 'invalid')
|
||||
])
|
||||
email = models.EmailField(_('email address'), blank=True)
|
||||
email = models.EmailField(_('email address'), max_length=255, blank=True, unique=True)
|
||||
is_active = models.BooleanField(_('active'), default=True,
|
||||
help_text=_('Designates whether this user should be treated as '
|
||||
'active. Unselect this instead of deleting accounts.'))
|
||||
|
|
Loading…
Reference in New Issue