diff --git a/taiga/auth/services.py b/taiga/auth/services.py index 72589eac..33a82baa 100644 --- a/taiga/auth/services.py +++ b/taiga/auth/services.py @@ -58,7 +58,7 @@ def send_register_email(user) -> bool: cancel_token = get_token_for_user(user, "cancel_account") context = {"user": user, "cancel_token": cancel_token} mbuilder = MagicMailBuilder(template_mail_cls=InlineCSSTemplateMail) - email = mbuilder.registered_user(user.email, context) + email = mbuilder.registered_user(user, context) return bool(email.send()) diff --git a/taiga/base/management/commands/test_emails.py b/taiga/base/management/commands/test_emails.py index 29752dd4..da535c7f 100644 --- a/taiga/base/management/commands/test_emails.py +++ b/taiga/base/management/commands/test_emails.py @@ -16,6 +16,8 @@ import datetime +from optparse import make_option + from django.db.models.loading import get_model from django.core.management.base import BaseCommand from django.utils import timezone @@ -30,6 +32,11 @@ from taiga.users.models import User class Command(BaseCommand): args = '' + option_list = BaseCommand.option_list + ( + make_option('--locale', '-l', default=None, dest='locale', + help='Send emails in an specific language.'), + ) + help = 'Send an example of all emails' def handle(self, *args, **options): @@ -37,12 +44,13 @@ class Command(BaseCommand): print("Usage: ./manage.py test_emails ") return + locale = options.get('locale') test_email = args[0] mbuilder = MagicMailBuilder(template_mail_cls=InlineCSSTemplateMail) # Register email - context = {"user": User.objects.all().order_by("?").first(), "cancel_token": "cancel-token"} + context = {"lang": locale, "user": User.objects.all().order_by("?").first(), "cancel_token": "cancel-token"} email = mbuilder.registered_user(test_email, context) email.send() @@ -51,17 +59,18 @@ class Command(BaseCommand): membership.invited_by = User.objects.all().order_by("?").first() membership.invitation_extra_text = "Text example, Text example,\nText example,\n\nText example" - context = {"membership": membership} + context = {"lang": locale, "membership": membership} email = mbuilder.membership_invitation(test_email, context) email.send() # Membership notification - context = {"membership": Membership.objects.order_by("?").filter(user__isnull=False).first()} + context = {"lang": locale, "membership": Membership.objects.order_by("?").filter(user__isnull=False).first()} email = mbuilder.membership_notification(test_email, context) email.send() # Feedback context = { + "lang": locale, "feedback_entry": { "full_name": "Test full name", "email": "test@email.com", @@ -76,17 +85,18 @@ class Command(BaseCommand): email.send() # Password recovery - context = {"user": User.objects.all().order_by("?").first()} + context = {"lang": locale, "user": User.objects.all().order_by("?").first()} email = mbuilder.password_recovery(test_email, context) email.send() # Change email - context = {"user": User.objects.all().order_by("?").first()} + context = {"lang": locale, "user": User.objects.all().order_by("?").first()} email = mbuilder.change_email(test_email, context) email.send() # Export/Import emails context = { + "lang": locale, "user": User.objects.all().order_by("?").first(), "project": Project.objects.all().order_by("?").first(), "error_subject": "Error generating project dump", @@ -95,6 +105,7 @@ class Command(BaseCommand): email = mbuilder.export_error(test_email, context) email.send() context = { + "lang": locale, "user": User.objects.all().order_by("?").first(), "error_subject": "Error importing project dump", "error_message": "Error importing project dump", @@ -104,6 +115,7 @@ class Command(BaseCommand): deletion_date = timezone.now() + datetime.timedelta(seconds=60*60*24) context = { + "lang": locale, "url": "http://dummyurl.com", "user": User.objects.all().order_by("?").first(), "project": Project.objects.all().order_by("?").first(), @@ -113,6 +125,7 @@ class Command(BaseCommand): email.send() context = { + "lang": locale, "user": User.objects.all().order_by("?").first(), "project": Project.objects.all().order_by("?").first(), } @@ -139,6 +152,7 @@ class Command(BaseCommand): ] context = { + "lang": locale, "project": Project.objects.all().order_by("?").first(), "changer": User.objects.all().order_by("?").first(), "history_entries": HistoryEntry.objects.all().order_by("?")[0:5], diff --git a/taiga/export_import/tasks.py b/taiga/export_import/tasks.py index 635eb63e..29032861 100644 --- a/taiga/export_import/tasks.py +++ b/taiga/export_import/tasks.py @@ -49,7 +49,7 @@ def dump_project(self, user, project): "error_message": "Error generating project dump", "project": project } - email = mbuilder.export_error(user.email, ctx) + email = mbuilder.export_error(user, ctx) email.send() return @@ -60,7 +60,7 @@ def dump_project(self, user, project): "user": user, "deletion_date": deletion_date } - email = mbuilder.dump_project(user.email, ctx) + email = mbuilder.dump_project(user, ctx) email.send() @@ -81,10 +81,10 @@ def load_project_dump(user, dump): "error_subject": "Error loading project dump", "error_message": "Error loading project dump", } - email = mbuilder.import_error(user.email, ctx) + email = mbuilder.import_error(user, ctx) email.send() return ctx = {"user": user, "project": project} - email = mbuilder.load_dump(user.email, ctx) + email = mbuilder.load_dump(user, ctx) email.send() diff --git a/taiga/projects/notifications/services.py b/taiga/projects/notifications/services.py index 189d4f6a..b066da34 100644 --- a/taiga/projects/notifications/services.py +++ b/taiga/projects/notifications/services.py @@ -269,6 +269,7 @@ def send_sync_notifications(notification_id): for user in notification.notify_users.distinct(): context["user"] = user + context["lang"] = user.lang email.send(user.email, context) notification.delete() diff --git a/taiga/projects/services/invitations.py b/taiga/projects/services/invitations.py index 1d79e58d..4196612c 100644 --- a/taiga/projects/services/invitations.py +++ b/taiga/projects/services/invitations.py @@ -9,10 +9,11 @@ def send_invitation(invitation): mbuilder = MagicMailBuilder(template_mail_cls=InlineCSSTemplateMail) if invitation.user: template = mbuilder.membership_notification + email = template(invitation.user, {"membership": invitation}) else: template = mbuilder.membership_invitation + email = template(invitation.email, {"membership": invitation}) - email = template(invitation.email, {"membership": invitation}) email.send() diff --git a/taiga/users/admin.py b/taiga/users/admin.py index a3452616..b2cd50cf 100644 --- a/taiga/users/admin.py +++ b/taiga/users/admin.py @@ -48,7 +48,7 @@ class UserAdmin(DjangoUserAdmin): fieldsets = ( (None, {'fields': ('username', 'password')}), (_('Personal info'), {'fields': ('full_name', 'email', 'bio', 'photo')}), - (_('Extra info'), {'fields': ('color', 'default_language', 'default_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',)}), (_('Important dates'), {'fields': ('last_login', 'date_joined')}), ) diff --git a/taiga/users/api.py b/taiga/users/api.py index 93f79be3..36dff9b1 100644 --- a/taiga/users/api.py +++ b/taiga/users/api.py @@ -97,7 +97,7 @@ class UsersViewSet(ModelCrudViewSet): user.save(update_fields=["token"]) mbuilder = MagicMailBuilder(template_mail_cls=InlineCSSTemplateMail) - email = mbuilder.password_recovery(user.email, {"user": user}) + email = mbuilder.password_recovery(user, {"user": user}) email.send() return response.Ok({"detail": _("Mail sended successful!"), @@ -231,7 +231,8 @@ class UsersViewSet(ModelCrudViewSet): request.user.new_email = new_email request.user.save(update_fields=["email_token", "new_email"]) mbuilder = MagicMailBuilder(template_mail_cls=InlineCSSTemplateMail) - email = mbuilder.change_email(request.user.new_email, {"user": request.user}) + email = mbuilder.change_email(request.user.new_email, {"user": request.user, + "lang": request.user.lang}) email.send() return ret diff --git a/taiga/users/fixtures/initial_user.json b/taiga/users/fixtures/initial_user.json index e13bcb95..ed7833c1 100644 --- a/taiga/users/fixtures/initial_user.json +++ b/taiga/users/fixtures/initial_user.json @@ -5,12 +5,12 @@ "username": "admin", "full_name": "", "bio": "", - "default_language": "", + "lang": "", "color": "", "photo": "", "is_active": true, "colorize_tags": false, - "default_timezone": "", + "timezone": "", "is_superuser": true, "token": "", "last_login": "2013-04-04T07:36:09.880Z", diff --git a/taiga/users/migrations/0009_auto_20150326_1241.py b/taiga/users/migrations/0009_auto_20150326_1241.py new file mode 100644 index 00000000..d12b59f8 --- /dev/null +++ b/taiga/users/migrations/0009_auto_20150326_1241.py @@ -0,0 +1,24 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import models, migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('users', '0008_auto_20150213_1701'), + ] + + operations = [ + migrations.RenameField( + model_name='user', + old_name='default_language', + new_name='lang', + ), + migrations.RenameField( + model_name='user', + old_name='default_timezone', + new_name='timezone', + ), + ] diff --git a/taiga/users/models.py b/taiga/users/models.py index e2ffe491..3c4af001 100644 --- a/taiga/users/models.py +++ b/taiga/users/models.py @@ -116,10 +116,10 @@ class User(AbstractBaseUser, PermissionsMixin): max_length=500, null=True, blank=True, verbose_name=_("photo")) date_joined = models.DateTimeField(_('date joined'), default=timezone.now) - default_language = models.CharField(max_length=20, null=False, blank=True, default="", - verbose_name=_("default language")) - default_timezone = models.CharField(max_length=20, null=False, blank=True, default="", - verbose_name=_("default timezone")) + lang = models.CharField(max_length=20, null=False, blank=True, default="", + verbose_name=_("default language")) + timezone = models.CharField(max_length=20, null=False, blank=True, default="", + verbose_name=_("default timezone")) colorize_tags = models.BooleanField(null=False, blank=True, default=False, verbose_name=_("colorize tags")) token = models.CharField(max_length=200, null=True, blank=True, default=None, @@ -166,8 +166,8 @@ class User(AbstractBaseUser, PermissionsMixin): self.full_name = "Deleted user" self.color = "" self.bio = "" - self.default_language = "" - self.default_timezone = "" + self.lang = "" + self.timezone = "" self.colorize_tags = True self.token = None self.set_unusable_password() diff --git a/taiga/users/serializers.py b/taiga/users/serializers.py index 5bb0d1ff..63173f34 100644 --- a/taiga/users/serializers.py +++ b/taiga/users/serializers.py @@ -43,8 +43,8 @@ class UserSerializer(ModelSerializer): # IMPORTANT: Maintain the UserAdminSerializer Meta up to date # with this info (including there the email) fields = ("id", "username", "full_name", "full_name_display", - "color", "bio", "default_language", - "default_timezone", "is_active", "photo", "big_photo") + "color", "bio", "lang", "timezone", "is_active", + "photo", "big_photo") read_only_fields = ("id",) def validate_username(self, attrs, source): @@ -81,8 +81,8 @@ class UserAdminSerializer(UserSerializer): # IMPORTANT: Maintain the UserSerializer Meta up to date # with this info (including here the email) fields = ("id", "username", "full_name", "full_name_display", "email", - "color", "bio", "default_language", - "default_timezone", "is_active", "photo", "big_photo") + "color", "bio", "lang", "timezone", "is_active", "photo", + "big_photo") read_only_fields = ("id", "email") diff --git a/tests/integration/test_memberships.py b/tests/integration/test_memberships.py index 9805006d..75d7f29a 100644 --- a/tests/integration/test_memberships.py +++ b/tests/integration/test_memberships.py @@ -80,7 +80,7 @@ def test_api_create_bulk_members_with_extra_text(client, outbox): def test_api_resend_invitation(client, outbox): - invitation = f.create_invitation() + invitation = f.create_invitation(user=None) f.MembershipFactory(project=invitation.project, user=invitation.project.owner, is_owner=True) url = reverse("memberships-resend-invitation", kwargs={"pk": invitation.pk})