diff --git a/esp/esp/dbmail/models.py b/esp/esp/dbmail/models.py index acc49a17cf..449eda3b7a 100644 --- a/esp/esp/dbmail/models.py +++ b/esp/esp/dbmail/models.py @@ -74,7 +74,7 @@ def send_mail(subject, message, from_email, recipient_list, fail_silently=False, from_email = from_email.strip() # the from_email must match one of our DMARC domains/subdomains # or the email may be rejected by email clients - if not re.match(r"(^.+@%s>?$)|(^.+@(\w+\.)?learningu\.org>?$)" % settings.SITE_INFO[1].replace(".", "\."), from_email): + if not re.match(r'(^.+@{0}$)|(^.+<.+@{0}>$)|(^.+@(\w+\.)?learningu\.org$)|(^.+<.+@(\w+\.)?learningu\.org>$)'.format(settings.SITE_INFO[1].replace('.', '\.')), from_email): raise ESPError("Invalid 'From' email address (" + from_email + "). The 'From' email address must " + "end in @" + settings.SITE_INFO[1] + " (your website), " + "@learningu.org, or a valid subdomain of learningu.org " + diff --git a/esp/esp/program/controllers/confirmation.py b/esp/esp/program/controllers/confirmation.py index aa15edae30..064a9e80c8 100644 --- a/esp/esp/program/controllers/confirmation.py +++ b/esp/esp/program/controllers/confirmation.py @@ -1,5 +1,4 @@ -from __future__ import absolute_import __author__ = "Individual contributors (see AUTHORS file)" __date__ = "$DATE$" __rev__ = "$REV$" @@ -37,13 +36,14 @@ from esp.program.models import Program, ClassSection, ClassSubject from esp.users.models import ESPUser, Record, RecordType from esp.program.modules.module_ext import DBReceipt +from esp.program.modules.forms.admincore import get_template_source from django.template import Template, Context from django.template.loader import select_template from esp.dbmail.models import send_mail class ConfirmationEmailController(object): - def send_confirmation_email(self, user, program, repeat=False, override=False): + def send_confirmation_email(self, user, program, repeat=False, override=False, context = {}): options = program.studentclassregmoduleinfo ## Get or create a userbit indicating whether or not email's been sent. try: @@ -52,12 +52,16 @@ def send_confirmation_email(self, user, program, repeat=False, override=False): except Exception: created = False if (created or repeat) and (options.send_confirmation or override): + context['user'] = user + context['program'] = program + receipt = select_template(['program/confemails/%s_custom_receipt.html' %(program.id), 'program/confemails/default.html']) + # render the custom pretext first try: - receipt_template = Template(DBReceipt.objects.get(program=program, action='confirmemail').receipt) - receipt_text = receipt_template.render(Context({'user': user, 'program': program})) - except: - receipt_template = select_template(['program/confemails/%s_confemail.txt' %(program.id), 'program/confemails/default.txt']) - receipt_text = receipt_template.render({'user': user, 'program': program}) + pretext = DBReceipt.objects.get(program=program, action='confirmemail').receipt + except DBReceipt.DoesNotExist: + pretext = get_template_source(['program/confemails/%s_custom_pretext.html' %(program.id), 'program/confemails/default_pretext.html']) + context['pretext'] = Template(pretext).render( Context(context, autoescape=False) ) + receipt_text = receipt.render( context ) send_mail("Thank you for registering for %s!" %(program.niceName()), \ receipt_text, \ (ESPUser.email_sendto_address(program.director_email, program.niceName() + " Directors")), \ diff --git a/esp/esp/program/migrations/0027_auto_20240220_2124.py b/esp/esp/program/migrations/0027_auto_20240220_2124.py index 82c854de1e..863f19d37f 100644 --- a/esp/esp/program/migrations/0027_auto_20240220_2124.py +++ b/esp/esp/program/migrations/0027_auto_20240220_2124.py @@ -11,7 +11,7 @@ def replace_director_emails(apps, schema_editor): Program = apps.get_model('program', 'Program') for prog in Program.objects.all(): - if not re.match(r"(^.+@%s$)|(^.+@(\w+\.)*learningu\.org$)" % settings.SITE_INFO[1].replace(".", "\."), prog.director_email): + if not re.match(r'(^.+@{0}$)|(^.+@(\w+\.)?learningu\.org$)'.format(settings.SITE_INFO[1].replace('.', '\.')), prog.director_email): prog.director_email = 'info@' + settings.SITE_INFO[1] prog.save() @@ -35,7 +35,7 @@ class Migration(migrations.Migration): help_text='The director email address must end in @' + settings.SITE_INFO[1] + ' (your website), @learningu.org, or a valid subdomain of learningu.org (i.e., @subdomain.learningu.org). The default is info@' + settings.SITE_INFO[1] + ', which redirects to the "default" email address from your site\'s settings by default. You can create and manage your email redirects here.', - validators=[validators.RegexValidator(r'(^.+@%s$)|(^.+@(\w+\.)?learningu\.org$)' % settings.SITE_INFO[1].replace('.', '\.'))]), + validators=[validators.RegexValidator(r'(^.+@{0}$)|(^.+<.+@{0}>$)|(^.+@(\w+\.)?learningu\.org$)|(^.+<.+@(\w+\.)?learningu\.org>$)'.format(settings.SITE_INFO[1].replace('.', '\.')))]), ), # This will run backwards, but won't do anything migrations.RunPython(replace_director_emails, lambda a, s: None), diff --git a/esp/esp/program/models/__init__.py b/esp/esp/program/models/__init__.py index 58bb8d436c..44edb6fa79 100644 --- a/esp/esp/program/models/__init__.py +++ b/esp/esp/program/models/__init__.py @@ -271,7 +271,7 @@ class Program(models.Model, CustomFormsLinkModel): grade_max = models.IntegerField() # director contact email address used for from field and display director_email = models.EmailField(default='info@' + settings.SITE_INFO[1], max_length=75, - validators=[validators.RegexValidator(r'(^.+@%s$)|(^.+@(\w+\.)?learningu\.org$)' % settings.SITE_INFO[1].replace('.', '\.'))], + validators=[validators.RegexValidator(r'(^.+@{0}$)|(^.+@(\w+\.)?learningu\.org$)'.format(settings.SITE_INFO[1].replace('.', '\.')))], help_text=mark_safe('The director email address must end in @' + settings.SITE_INFO[1] + ' (your website), ' + '@learningu.org, or a valid subdomain of learningu.org (i.e., @subdomain.learningu.org). ' + 'The default is info@' + settings.SITE_INFO[1] + ', which redirects to the "default" ' + diff --git a/esp/esp/program/modules/forms/admincore.py b/esp/esp/program/modules/forms/admincore.py index 7a03627d94..ed1c41f9c9 100644 --- a/esp/esp/program/modules/forms/admincore.py +++ b/esp/esp/program/modules/forms/admincore.py @@ -1,4 +1,3 @@ -from __future__ import absolute_import from decimal import Decimal from django import forms from django.conf import settings @@ -19,7 +18,7 @@ from esp.utils.models import TemplateOverride def get_rt_choices(): - choices = [("All", "All")] + choices = [("All","All")] for rt in RegistrationType.objects.all().order_by('name'): if rt.displayName: choices.append((rt.name, '%s (displayed as "%s")' % (rt.name, rt.displayName))) @@ -91,16 +90,16 @@ def save(self): class Meta: fieldsets = [ ('Program Title', {'fields': ['term', 'term_friendly'] }), - ('Program Constraints', {'fields':['grade_min', 'grade_max', 'program_size_max', 'program_allow_waitlist']}), - ('About Program Creator', {'fields':['director_email', 'director_cc_email', 'director_confidential_email']}), - ('Financial Details', {'fields':['base_cost', 'sibling_discount']}), - ('Program Internal Details', {'fields':['program_type', 'program_modules', 'program_module_questions', 'class_categories', 'flag_types']}), + ('Program Constraints', {'fields':['grade_min','grade_max','program_size_max','program_allow_waitlist']}), + ('About Program Creator',{'fields':['director_email', 'director_cc_email', 'director_confidential_email']}), + ('Financial Details' ,{'fields':['base_cost','sibling_discount']}), + ('Program Internal Details' ,{'fields':['program_type','program_modules','program_module_questions','class_categories','flag_types']}), ]# Here you can also add description for each fieldset. widgets = { 'program_modules': forms.SelectMultiple(attrs={'class': 'hidden-field'}), } model = Program -ProgramSettingsForm.base_fields['director_email'].widget = forms.EmailInput(attrs={'pattern': r'(^.+@%s$)|(^.+@(\w+\.)?learningu\.org$)' % settings.SITE_INFO[1].replace('.', '\.')}) +ProgramSettingsForm.base_fields['director_email'].widget = forms.EmailInput(attrs={'pattern': r'(^.+@{0}$)|(^.+@(\w+\.)?learningu\.org$)'.format(settings.SITE_INFO[1].replace('.', '\.'))}) class TeacherRegSettingsForm(BetterModelForm): """ Form for changing teacher class registration settings. """ @@ -128,7 +127,7 @@ class Meta: ('Priority Registration Settings', {'fields': ['priority_limit']}), # use_priority is not included here to prevent confusion; to my knowledge, only HSSP uses this setting - WG ('Enrollment Settings', {'fields': ['register_from_catalog', 'visible_enrollments', 'visible_meeting_times', 'show_emailcodes']}), # use_grade_range_exceptions is excluded until there is an interface for it - WG 5/25/23 ('Button Settings', {'fields': ['confirm_button_text', 'view_button_text', 'cancel_button_text', 'temporarily_full_text', 'cancel_button_dereg', 'send_confirmation']}), - ('Visual Options', {'fields': ['progress_mode', 'force_show_required_modules']}), + ('Visual Options', {'fields': ['progress_mode','force_show_required_modules']}), ]# Here you can also add description for each fieldset. model = StudentClassRegModuleInfo @@ -141,14 +140,16 @@ def get_template_source(template_list): class ReceiptsForm(BetterForm): confirm = forms.CharField(widget=forms.Textarea(attrs={'class': 'fullwidth'}), - help_text = "This text is shown on the website when a student clicks the 'confirm registration' button (HTML is supported).\ - If no text is supplied, the default text will be used. The text is then followed by the student's information,\ - the program information, the student's purchased items, and the student's schedule.", + help_text = mark_safe("This text is shown on the website when a student clicks the 'confirm registration' button (HTML is supported).\ + If no text is supplied, the default text will be used. The text is then followed by the student's information,\ + the program information, the student's purchased items, and the student's schedule."), required = False) confirmemail = forms.CharField(widget=forms.Textarea(attrs={'class': 'fullwidth'}), - help_text = "This receipt is sent via email when a student clicks the 'confirm registration' button.\ - If no text is supplied, the default text will be used.", - required = False) + help_text = mark_safe("This text is sent via email when a student clicks the 'confirm registration' button.\ + If no text is supplied, the default text will be used. The text is then followed by the student's information,\ + the program information, the student's purchased items, and the student's schedule. This email can be disabled\ + by deactivating the 'Send confirmation' option in the 'Student Registration Settings' above."), + required = False) cancel = forms.CharField(widget=forms.Textarea(attrs={'class': 'fullwidth'}), help_text = "This receipt is shown on the website when a student clicks the 'cancel registration' button.\ If no text is supplied, the student will be redirected to the main student registration page instead.", @@ -164,7 +165,7 @@ def __init__(self, *args, **kwargs): elif action == "confirm": receipt_text = get_template_source(['program/receipts/%s_custom_pretext.html' %(self.program.id), 'program/receipts/default_pretext.html']) elif action == "confirmemail": - receipt_text = get_template_source(['program/confemails/%s_confemail.txt' %(self.program.id), 'program/confemails/default.txt']) + receipt_text = get_template_source(['program/confemails/%s_confemail_pretext.html' %(self.program.id),'program/confemails/default_pretext.html']) else: receipt_text = "" self.fields[action].initial = receipt_text.encode('UTF-8') @@ -179,7 +180,7 @@ def save(self): if action == "confirm": default_text = get_template_source(['program/receipts/%s_custom_pretext.html' %(self.program.id), 'program/receipts/default_pretext.html']) elif action == "confirmemail": - default_text = get_template_source(['program/confemails/%s_confemail.txt' %(self.program.id), 'program/confemails/default.txt']) + default_text = get_template_source(['program/confemails/%s_confemail_pretext.html' %(self.program.id),'program/confemails/default_pretext.html']) elif action == "cancel": default_text = "" if cleaned_text == default_text: @@ -226,7 +227,7 @@ def __init__(self, *args, **kwargs): self.fields[key].initial = self.fields[key].default = tag_info.get('default') self.fields[key].required = False set_val = Tag.getBooleanTag(key, program = self.program) if tag_info.get('is_boolean', False) else Tag.getProgramTag(key, program = self.program) - if set_val is not None and set_val != self.fields[key].initial: + if set_val != None and set_val != self.fields[key].initial: if isinstance(self.fields[key], forms.MultipleChoiceField): set_val = set_val.split(",") self.fields[key].initial = set_val diff --git a/esp/esp/program/modules/handlers/commmodule.py b/esp/esp/program/modules/handlers/commmodule.py index 06fa7797a2..a6ee70876e 100644 --- a/esp/esp/program/modules/handlers/commmodule.py +++ b/esp/esp/program/modules/handlers/commmodule.py @@ -41,6 +41,7 @@ from esp.users.controllers.usersearch import UserSearchController from esp.users.views.usersearch import get_user_checklist from esp.dbmail.models import ActionHandler +from esp.tagdict.models import Tag from django.template import Template from django.template import Context as DjangoContext from esp.middleware import ESPError @@ -81,7 +82,7 @@ def commprev(self, request, tl, one, two, module, extra, prog): # Set From address if request.POST.get('from', '').strip(): fromemail = request.POST['from'] - if not re.match(r"(^.+@%s$)|(^.+@(\w+\.)?learningu\.org$)" % settings.SITE_INFO[1].replace(".", "\."), fromemail): + if not re.match(r'(^.+@{0}$)|(^.+<.+@{0}>$)|(^.+@(\w+\.)?learningu\.org$)|(^.+<.+@(\w+\.)?learningu\.org>$)'.format(settings.SITE_INFO[1].replace('.', '\.')), fromemail): raise ESPError("Invalid 'From' email address. The 'From' email address must " + "end in @" + settings.SITE_INFO[1] + " (your website), " + "@learningu.org, or a valid subdomain of learningu.org " + @@ -91,7 +92,8 @@ def commprev(self, request, tl, one, two, module, extra, prog): prs = PlainRedirect.objects.filter(original = "info") if not prs.exists(): redirect = PlainRedirect.objects.create(original = "info", destination = settings.DEFAULT_EMAIL_ADDRESSES['default']) - fromemail = '%s@%s' % ("info", settings.SITE_INFO[1]) + fromemail = '%s <%s@%s>' % (Tag.getTag('full_group_name') or '%s %s' % (settings.INSTITUTION_NAME, settings.ORGANIZATION_SHORT_NAME), + "info", settings.SITE_INFO[1]) # Set Reply-To address if request.POST.get('replyto', '').strip(): @@ -254,7 +256,8 @@ def commpanel(self, request, tl, one, two, module, extra, prog): if request.method == 'POST': # Turn multi-valued QueryDict into standard dictionary data = ListGenModule.processPost(request) - + context['default_from'] = '%s <%s@%s>' % (Tag.getTag('full_group_name') or '%s %s' % (settings.INSTITUTION_NAME, settings.ORGANIZATION_SHORT_NAME), + "info", settings.SITE_INFO[1]) ## Handle normal list selecting submissions if ('base_list' in data and 'recipient_type' in data) or ('combo_base_list' in data): @@ -275,7 +278,7 @@ def commpanel(self, request, tl, one, two, module, extra, prog): prs = PlainRedirect.objects.filter(original = "info") if not prs.exists(): redirect = PlainRedirect.objects.create(original = "info", destination = settings.DEFAULT_EMAIL_ADDRESSES['default']) - context['from'] = '%s@%s' % ("info", settings.SITE_INFO[1]) + context['from'] = context['default_from'] return render_to_response(self.baseDir()+'step2.html', request, context) ## Prepare a message starting from an earlier request diff --git a/esp/esp/program/modules/handlers/studentregcore.py b/esp/esp/program/modules/handlers/studentregcore.py index 625f35757e..9ff555a42a 100644 --- a/esp/esp/program/modules/handlers/studentregcore.py +++ b/esp/esp/program/modules/handlers/studentregcore.py @@ -206,12 +206,13 @@ def confirmreg_forreal(self, request, tl, one, two, module, extra, prog, new_reg else: raise ESPError("You must finish all the necessary steps first, then click on the Save button to finish registration.", log=False) - cfe = ConfirmationEmailController() - cfe.send_confirmation_email(user, self.program) - # when does class registration close for this user? context['deadline'] = Permission.user_deadline_when(user, "Student/Classes", prog) + cfe = ConfirmationEmailController() + # this email includes the student's schedule (by default), so send a new email each time they confirm their reg + cfe.send_confirmation_email(user, self.program, context = context, repeat = True) + context["request"] = request context["program"] = prog context.update(esp_context_stuff()) diff --git a/esp/esp/program/modules/migrations/0044_auto_20240507_2041.py b/esp/esp/program/modules/migrations/0044_auto_20240507_2041.py new file mode 100644 index 0000000000..4527630eab --- /dev/null +++ b/esp/esp/program/modules/migrations/0044_auto_20240507_2041.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.29 on 2024-05-07 20:41 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('modules', '0043_auto_20240321_2023'), + ] + + operations = [ + migrations.AlterField( + model_name='studentclassregmoduleinfo', + name='send_confirmation', + field=models.BooleanField(default=True, help_text=b'Check this box to send each student an email each time they confirm their registration. You can customize the text of the email using the "Confirmemail" registration receipt below.'), + ), + ] diff --git a/esp/esp/program/modules/migrations/0045_merge_20240515_2025.py b/esp/esp/program/modules/migrations/0045_merge_20240515_2025.py new file mode 100644 index 0000000000..a8cc3cd789 --- /dev/null +++ b/esp/esp/program/modules/migrations/0045_merge_20240515_2025.py @@ -0,0 +1,17 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.29 on 2024-05-15 20:25 +from __future__ import unicode_literals + +from __future__ import absolute_import +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('modules', '0044_auto_20240507_2041'), + ('modules', '0044_auto_20240509_2341'), + ] + + operations = [ + ] diff --git a/esp/esp/program/modules/module_ext.py b/esp/esp/program/modules/module_ext.py index 89b55742d9..6baf4b825e 100644 --- a/esp/esp/program/modules/module_ext.py +++ b/esp/esp/program/modules/module_ext.py @@ -116,7 +116,7 @@ class StudentClassRegModuleInfo(models.Model): progress_mode = models.IntegerField(default=1, help_text='Select which to use on student reg: 1=checkboxes, 2=progress bar, 0=neither.') # Choose whether an email is sent the first time a student confirms registration. - send_confirmation = models.BooleanField(default=False, help_text='Check this box to send each student an email the first time they confirm their registration. You must define an associated DBReceipt of type "confirmemail".') + send_confirmation = models.BooleanField(default=True, help_text='Check this box to send each student an email each time they confirm their registration. You can customize the text of the email using the "Confirmemail" registration receipt below.') # Choose whether class IDs are shown on catalog. show_emailcodes = models.BooleanField(default=True, help_text='Uncheck this box to prevent email codes (i.e. E534, H243) from showing up on catalog and fillslot pages.') diff --git a/esp/esp/users/models/__init__.py b/esp/esp/users/models/__init__.py index ad1cd5fc13..4cf7a79c69 100644 --- a/esp/esp/users/models/__init__.py +++ b/esp/esp/users/models/__init__.py @@ -2948,7 +2948,7 @@ def send_confirmation_email(self): subject, message = self._confirmation_email_content() send_mail(subject, message, - 'info@' + settings.SITE_INFO[1], + Tag.getTag('full_group_name') or '%s %s' % (settings.INSTITUTION_NAME, settings.ORGANIZATION_SHORT_NAME) + ' ', [self.requesting_student.email, ]) def get_admin_url(self): diff --git a/esp/templates/program/confemails/default.html b/esp/templates/program/confemails/default.html new file mode 100644 index 0000000000..c9573eade8 --- /dev/null +++ b/esp/templates/program/confemails/default.html @@ -0,0 +1,135 @@ + + + + + + +{% autoescape off %} +{{ pretext }} +{% endautoescape %} + +
+ +

+User Information:
+

+

+ +

+Program Information:
+

+

+ +

+Payment Information:
+

+

+ +
+ + + + +{% for timeslot in timeslots %} + {% ifchanged timeslot.0.start.day %} + + {% endifchanged %} + {% ifchanged timeslot.2 %} + + {% endifchanged %} + + {% ifequal timeslot.0.event_type.description "Compulsory" %} + + + + {% else %} + + {% if timeslot.1|length_is:0 %} + + {% elif timeslot.1.0.first_meeting_time %} + + {% endif %} + + {% endifequal %} +{% endfor %} +
+ Classes for {{ user.first_name }} {{ user.last_name }} - ID: {{ user.id }} +
Classes beginning on {{ timeslot.0.start|date:"D M j Y" }}
Block {{ timeslot.2 }}
{{ timeslot.0.short_description }}{{ timeslot.0.description }}
{{ timeslot.0.short_description }} + No classes + + {% for cls in timeslot.1 %} + {% comment %}{% if use_priority %}{% endcomment %} + {% if not cls.section.verbs|length_is:0 %} + {% for v in cls.section.verbs %}{{ v }}{% if not forloop.last %}, {% endif %}{% endfor %}: + {% endif %} + {% comment %}{% endif %}{% endcomment %} + {{ cls.section }}{% if not cls.first_meeting_time %} (continued){% endif %} + + {% if request.user.onsite_local %} + ({{ cls.section.prettyrooms|join:", " }}) + {% endif %} + {% if not forloop.last %}
{% endif %} + {% endfor %} +
+ +
+ + + + diff --git a/esp/templates/program/confemails/default.txt b/esp/templates/program/confemails/default.txt deleted file mode 100644 index 205d7b11bc..0000000000 --- a/esp/templates/program/confemails/default.txt +++ /dev/null @@ -1,7 +0,0 @@ -Dear {{ user.first_name }}, - -Thank you for registering for {{ program.niceName }}! This is a preliminary confirmation email for your {{ program.program_type }} registration; you can come back and change your classes until registration closes. We will contact you via email with further information. - -{{ program.niceName }} Directors - - diff --git a/esp/templates/program/confemails/default_pretext.html b/esp/templates/program/confemails/default_pretext.html new file mode 100644 index 0000000000..e434e36be2 --- /dev/null +++ b/esp/templates/program/confemails/default_pretext.html @@ -0,0 +1,5 @@ +

Dear {{ user.first_name }},

+ +

Thank you for registering for {{ program.niceName }}! This is a preliminary confirmation email for your {{ program.program_type }} registration. Your registration details are included below. You can come back and change your classes until registration closes. We will contact you via email with further information.

+ +

{{ program.niceName }} Directors

\ No newline at end of file diff --git a/esp/templates/program/modules/commmodule/step2.html b/esp/templates/program/modules/commmodule/step2.html index b0ca1684c0..5d21d0a49e 100644 --- a/esp/templates/program/modules/commmodule/step2.html +++ b/esp/templates/program/modules/commmodule/step2.html @@ -42,6 +42,7 @@

Step 2:

By default the "From" email address is info@{{ current_site.domain }}, which redirects to the "default" email address from your site's settings. You can create and manage your email redirects here. The "Reply-To" field can be any email address (by default it is the same as the "From" email address). +Both email address fields also support named "mailboxes", such as "{{ default_from }}".
@@ -51,9 +52,9 @@

Step 2:

- +