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
Jesús Espino 2014-09-17 15:10:27 +02:00
commit 6928c39281
8 changed files with 127 additions and 64 deletions

View File

@ -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

View File

@ -185,53 +185,30 @@ 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():
if not serializer.is_valid():
return response.BadRequest(serializer.errors)
data = serializer.data
project = models.Project.objects.get(id=data["project_id"])
self.check_permissions(request, 'bulk_create', project)
# TODO: this should be moved to main exception handler instead
# of handling explicit exception catchin here.
try:
members = services.create_members_in_bulk(
data["bulk_memberships"], project=project, callback=self.post_save,
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)
return response.BadRequest(err.message_dict)
members_serialized = self.serializer_class(members, many=True)
return response.Ok(data=members_serialized.data)
return response.BadRequest(serializer.errors)
@detail_route(methods=["POST"])
def resend_invitation(self, request, **kwargs):
invitation = self.get_object()
@ -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)

View File

@ -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',
),
]

View File

@ -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"))

View File

@ -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):

View File

@ -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

View File

@ -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'),
),
]

View File

@ -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.'))