diff --git a/CHANGELOG.md b/CHANGELOG.md index ed182dab..7e9b8705 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,11 @@ Changelog ========= +next +---- +* Add unique `identifier` field to model `EmailTemplate`. It can be used to access templates from Python code + and leaves the `name` field as a human readable representation of that template. + Version 3.5.2 (2020-11-05) -------------------------- * Fixed an issue where Post Office's admin interface doesn't show. Thanks @christianciu! diff --git a/post_office/admin.py b/post_office/admin.py index d9c0b74c..eb60d0f6 100644 --- a/post_office/admin.py +++ b/post_office/admin.py @@ -14,7 +14,7 @@ from django.urls import reverse from django.utils.html import format_html from django.utils.text import Truncator -from django.utils.translation import gettext_lazy as _ +from django.utils.translation import gettext_lazy as _, get_language from .fields import CommaSeparatedEmailField from .models import Attachment, Log, Email, EmailTemplate, STATUS @@ -241,7 +241,7 @@ class EmailTemplateAdminForm(forms.ModelForm): class Meta: model = EmailTemplate - fields = ['name', 'description', 'subject', 'content', 'html_content', 'language', + fields = ['language', 'name', 'description', 'subject', 'content', 'html_content', 'default_template'] def __init__(self, *args, **kwargs): @@ -250,13 +250,21 @@ def __init__(self, *args, **kwargs): if instance and instance.language: self.fields['language'].disabled = True + def get_initial_for_field(self, field, field_name): + """ + For new translated templates, copy the name from the default email template. + """ + if field_name == 'name' and self.instance and self.instance.id is None and 'default_template' in self.fields: + return self.fields['default_template'].parent_instance.name + return super().get_initial_for_field(field, field_name) + class EmailTemplateInline(admin.StackedInline): form = EmailTemplateAdminForm formset = EmailTemplateAdminFormSet model = EmailTemplate extra = 0 - fields = ('language', 'subject', 'content', 'html_content',) + fields = ['language', 'name', 'description', 'subject', 'content', 'html_content'] formfield_overrides = { models.CharField: {'widget': SubjectField} } @@ -268,11 +276,11 @@ def get_max_num(self, request, obj=None, **kwargs): # @admin.register(EmailTemplate) class EmailTemplateAdmin(admin.ModelAdmin): form = EmailTemplateAdminForm - list_display = ('name', 'description_shortened', 'subject', 'languages_compact', 'created') - search_fields = ('name', 'description', 'subject') + list_display = ['translated_name', 'identifier', 'subject', 'languages_compact', 'created'] + search_fields = ['name', 'identifier', 'description', 'subject'] fieldsets = [ (None, { - 'fields': ('name', 'description'), + 'fields': ('name', 'description', 'identifier'), }), (_("Default Content"), { 'fields': ('subject', 'content', 'html_content'), @@ -286,10 +294,13 @@ class EmailTemplateAdmin(admin.ModelAdmin): def get_queryset(self, request): return self.model.objects.filter(default_template__isnull=True) - def description_shortened(self, instance): - return Truncator(instance.description.split('\n')[0]).chars(200) - description_shortened.short_description = _("Description") - description_shortened.admin_order_field = 'description' + def translated_name(self, instance): + try: + return instance.translated_templates.get(language=get_language()).name + except EmailTemplate.DoesNotExist: + return instance.name + translated_name.short_description = _("Name") + translated_name.admin_order_field = 'name' def languages_compact(self, instance): languages = [tt.language for tt in instance.translated_templates.order_by('language')] diff --git a/post_office/migrations/0010_auto_20200723_1131.py b/post_office/migrations/0010_auto_20200723_1131.py new file mode 100644 index 00000000..24c25ded --- /dev/null +++ b/post_office/migrations/0010_auto_20200723_1131.py @@ -0,0 +1,37 @@ +# Generated by Django 2.2.10 on 2020-07-23 09:31 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('post_office', '0009_requeued_mode'), + ] + + operations = [ + migrations.AddField( + model_name='emailtemplate', + name='identifier', + field=models.CharField(blank=True, help_text='Unique template identifier', max_length=50, null=True, unique=True, verbose_name='Identifier'), + ), + migrations.AlterField( + model_name='attachment', + name='emails', + field=models.ManyToManyField(related_name='attachments', to='post_office.Email', verbose_name='Emails'), + ), + migrations.AlterField( + model_name='email', + name='expires_at', + field=models.DateTimeField(blank=True, help_text="Email won't be sent after this timestamp", null=True, verbose_name='Expires'), + ), + migrations.AlterField( + model_name='email', + name='scheduled_time', + field=models.DateTimeField(blank=True, db_index=True, help_text='The scheduled sending time', null=True, verbose_name='Scheduled Time'), + ), + migrations.AlterUniqueTogether( + name='emailtemplate', + unique_together=set(), + ), + ] diff --git a/post_office/models.py b/post_office/models.py index 0fccaea8..e8440cbb 100644 --- a/post_office/models.py +++ b/post_office/models.py @@ -246,6 +246,8 @@ class EmailTemplate(models.Model): name = models.CharField(_('Name'), max_length=255, help_text=_("e.g: 'welcome_email'")) description = models.TextField(_('Description'), blank=True, help_text=_("Description of this template.")) + identifier = models.CharField(_('Identifier'), max_length=50, unique=True, blank=True, null=True, + help_text=_("Unique template identifier")) created = models.DateTimeField(auto_now_add=True) last_updated = models.DateTimeField(auto_now=True) subject = models.CharField(max_length=255, blank=True, @@ -265,7 +267,6 @@ class EmailTemplate(models.Model): class Meta: app_label = 'post_office' - unique_together = ('name', 'language', 'default_template') verbose_name = _("Email Template") verbose_name_plural = _("Email Templates") ordering = ['name'] @@ -277,10 +278,6 @@ def natural_key(self): return (self.name, self.language, self.default_template) def save(self, *args, **kwargs): - # If template is a translation, use default template's name - if self.default_template and not self.name: - self.name = self.default_template.name - template = super().save(*args, **kwargs) cache.delete(self.name) return template diff --git a/post_office/tests/test_models.py b/post_office/tests/test_models.py index 77ec5202..e3aee817 100644 --- a/post_office/tests/test_models.py +++ b/post_office/tests/test_models.py @@ -311,9 +311,10 @@ def test_attachments_email_message_with_mimetype(self): [('test.txt', 'test file content', 'text/plain')]) def test_translated_template_uses_default_templates_name(self): - template = EmailTemplate.objects.create(name='name') - id_template = template.translated_templates.create(language='id') - self.assertEqual(id_template.name, template.name) + template = EmailTemplate.objects.create(name='name', identifier='identifier') + id_template = template.translated_templates.create(name='Nama', language='id') + self.assertEqual(id_template.name, 'Nama') + self.assertEqual(id_template.identifier, None) def test_models_repr(self): self.assertEqual(repr(EmailTemplate(name='test', language='en')),