Great leaps towards completing the mailing list request tool.
- Legacy-Id: 259
This commit is contained in:
parent
a99d598cc0
commit
3cd72bc1fe
|
@ -43,17 +43,27 @@ class ListReqStep1(forms.Form):
|
|||
('movenon', 'Move existing non-WG email list to selected domain above'),
|
||||
('closenon', 'Close existing non-WG email list at selected domain above'),
|
||||
), widget=forms.RadioSelect)
|
||||
group = forms.ChoiceField(required=False)
|
||||
#group = forms.ChoiceField(required=False)
|
||||
group = forms.ModelChoiceField(queryset=IETFWG.objects.all().select_related().order_by('acronym.acronym'), required=False, empty_label="-- Select Working Group")
|
||||
domain_name = forms.ChoiceField(choices=DOMAIN_CHOICES, required=False)
|
||||
list_to_close = forms.ChoiceField(required=False)
|
||||
list_to_close = forms.ModelChoiceField(queryset=ImportedMailingList.objects.all(), required=False, empty_label="-- Select List To Close")
|
||||
def mail_type_fields(self):
|
||||
field = self['mail_type']
|
||||
return field.as_widget(field.field.widget)
|
||||
def __init__(self, *args, **kwargs):
|
||||
dname = kwargs.get('dname', 'ietf.org')
|
||||
dname = 'ietf.org'
|
||||
if args:
|
||||
dn = 'domain_name'
|
||||
if kwargs.has_key('prefix'):
|
||||
dn = kwargs['prefix'] + '-' + dn
|
||||
dname = args[0][dn]
|
||||
dname = kwargs.get('dname', dname)
|
||||
super(ListReqStep1, self).__init__(*args, **kwargs)
|
||||
self.fields['group'].choices = [('', '-- Select Working Group')] + IETFWG.choices()
|
||||
self.fields['list_to_close'].choices = [('', '-- Select List To Close')] + ImportedMailingList.choices(dname)
|
||||
#self.fields['group'].choices = [('', '-- Select Working Group')] + IETFWG.choices()
|
||||
#self.fields['list_to_close'].choices = [('', '-- Select List To Close')] + ImportedMailingList.choices(dname)
|
||||
#XXX This doesn't work yet. Maybe switch back to choices.
|
||||
self.fields['list_to_close'].queryset = ImportedMailingList.choices(dname)
|
||||
print "dname %s list_to_close values: %s" % (dname, self.fields['list_to_close'].queryset)
|
||||
self.fields['domain_name'].initial = dname
|
||||
def clean_group(self):
|
||||
group = self.clean_data['group']
|
||||
|
@ -61,15 +71,13 @@ class ListReqStep1(forms.Form):
|
|||
if action.endswith('wg'):
|
||||
if not self.clean_data.get('group'):
|
||||
raise forms.ValidationError, 'Please pick a working group'
|
||||
group_name = Acronym.objects.get(pk=group).acronym
|
||||
#group_list_exists = ImportedMailingList.objects.filter(acronym=group_name).count()
|
||||
group_list_exists = ImportedMailingList.objects.filter(group_acronym=group).count()
|
||||
if action.startswith('close'):
|
||||
if group_list_exists == 0:
|
||||
raise forms.ValidationError, 'The %s mailing list does not exist.' % group_name
|
||||
raise forms.ValidationError, 'The %s mailing list does not exist.' % group
|
||||
else:
|
||||
if group_list_exists:
|
||||
raise forms.ValidationError, 'The %s mailing list already exists.' % group_name
|
||||
raise forms.ValidationError, 'The %s mailing list already exists.' % group
|
||||
return self.clean_data['group']
|
||||
def clean_list_to_close(self):
|
||||
if self.clean_data.get('mail_type', '') == 'closenon':
|
||||
|
@ -126,6 +134,21 @@ class PickApprover(forms.Form):
|
|||
super(PickApprover, self).__init__(*args, **kwargs)
|
||||
self.fields['approver'].choices = [('', '-- Pick an approver from the list below')] + [(person.person_or_org_tag, str(person)) for person in PersonOrOrgInfo.objects.filter(pk__in=approvers)]
|
||||
|
||||
class ListApprover(forms.Form):
|
||||
"""
|
||||
When instantiating, supply a list of AreaDirector, WGChair and/or Role
|
||||
objects (or other objects with a person_id and appropriate str value).
|
||||
"""
|
||||
approver = forms.ChoiceField(choices=(
|
||||
('', '-- Pick an approver from the list below'),
|
||||
))
|
||||
def __init__(self, approvers, requestor=None, *args, **kwargs):
|
||||
super(ListApprover, self).__init__(*args, **kwargs)
|
||||
self.fields['approver'].choices = [('', '-- Pick an approver from the list below')] + [(item.person_id, str(item)) for item in approvers]
|
||||
if requestor:
|
||||
self.fields['approver'].initial = requestor.person_id
|
||||
self.fields['approver'].widget = forms.widgets.HiddenInput()
|
||||
|
||||
class DeletionPickApprover(PickApprover):
|
||||
ds_name = forms.CharField(label = 'Enter your name', widget = forms.TextInput(attrs = {'size': 45}))
|
||||
ds_email = forms.EmailField(label = 'Enter your email', widget = forms.TextInput(attrs = {'size': 45}))
|
||||
|
@ -143,9 +166,11 @@ class ListReqAuthorized(forms.Form):
|
|||
raise forms.ValidationError, 'You must assert that you are authorized to perform this action.'
|
||||
return self.clean_data['authorized']
|
||||
|
||||
# subclass pickapprover here too?
|
||||
class ListReqClose(forms.Form):
|
||||
pass
|
||||
requestor = forms.CharField(label = "Requestor's full name", widget = forms.TextInput(attrs = {'size': 55}))
|
||||
requestor_email = forms.EmailField(label = "Requestor's email address", widget = forms.TextInput(attrs = {'size': 55}))
|
||||
#mlist_name: with a widget that just displays the value as text
|
||||
reason_to_delete = forms.CharField(label = 'Reason for closing list', widget = forms.Textarea(attrs = {'rows': 4, 'cols': 60}))
|
||||
|
||||
class AdminRequestor(forms.MultiWidget):
|
||||
def decompress(self, value):
|
||||
|
@ -169,7 +194,8 @@ class AdminRequestor(forms.MultiWidget):
|
|||
# this is used.
|
||||
key = name.replace('admins', 'requestor_email')
|
||||
try:
|
||||
return data[key] + "\n" + rest
|
||||
ret = data[key] + "\r\n" + rest
|
||||
return ret.strip()
|
||||
except KeyError:
|
||||
return rest
|
||||
else:
|
||||
|
|
|
@ -10,12 +10,13 @@ class ImportedMailingList(models.Model):
|
|||
name = models.CharField(blank=True, maxlength=255, db_column='list_name')
|
||||
domain = models.CharField(blank=True, maxlength=25, db_column='list_domain')
|
||||
def __str__(self):
|
||||
return self.name or self.group_acronym
|
||||
return self.acronym or self.group_acronym.acronym
|
||||
def choices(dname):
|
||||
objects = ImportedMailingList.objects.all().filter(domain__icontains=dname).exclude(acronym__iendswith='announce')
|
||||
if dname == "ietf.org":
|
||||
objects = objects.exclude(acronym__istartswith='ietf').exclude(acronym__icontains='iesg')
|
||||
return [(list.acronym, list.acronym) for list in objects]
|
||||
return objects
|
||||
#return [(list.acronym, list.acronym) for list in objects]
|
||||
choices = staticmethod(choices)
|
||||
class Meta:
|
||||
db_table = 'imported_mailing_list'
|
||||
|
@ -24,7 +25,9 @@ class ImportedMailingList(models.Model):
|
|||
|
||||
class Domain(models.Model):
|
||||
domain = models.CharField("Mailing List Domain Name", maxlength=100)
|
||||
approvers = models.ManyToManyField(Role)
|
||||
approvers = models.ManyToManyField(Role, filter_interface=models.HORIZONTAL)
|
||||
def __str__(self):
|
||||
return self.domain
|
||||
class Admin:
|
||||
pass
|
||||
|
||||
|
@ -34,6 +37,14 @@ class MailingList(models.Model):
|
|||
(2, 'Approval'),
|
||||
(3, 'Confirm+Approval'),
|
||||
)
|
||||
MAILTYPE_MAP = {
|
||||
'newwg': 1,
|
||||
'movewg': 2,
|
||||
'closewg': 5,
|
||||
'newnon': 4,
|
||||
'movenon': 3,
|
||||
'closenon': 6,
|
||||
}
|
||||
MAILTYPE_CHOICES = (
|
||||
(1, 'Create new WG email list at ietf.org'),
|
||||
(2, 'Move existing WG email list to ietf.org'),
|
||||
|
@ -87,7 +98,7 @@ class MailingList(models.Model):
|
|||
if self.mailing_list_id is None:
|
||||
generate = True
|
||||
while generate:
|
||||
self.mailing_list_id = ''.join([random.choice('0123456789abcdefghijklmnopqrstuvwxyz') for i in range(35)])
|
||||
self.mailing_list_id = ''.join([random.choice('0123456789abcdefghijklmnopqrstuvwxyz') for i in range(25)])
|
||||
try:
|
||||
MailingList.objects.get(pk=self.mailing_list_id)
|
||||
except MailingList.DoesNotExist:
|
||||
|
@ -110,7 +121,7 @@ class NonWgMailingList(models.Model):
|
|||
list_url = models.CharField("List URL", maxlength=255)
|
||||
admin = models.TextField("Administrator(s)' Email Address(es)", blank=True)
|
||||
purpose = models.TextField(blank=True)
|
||||
area = models.ForeignKey(Area, db_column='area_acronym_id', null=True)
|
||||
area = models.ForeignKey(Area, db_column='area_acronym_id')
|
||||
subscribe_url = models.CharField("Subscribe URL", blank=True, maxlength=255)
|
||||
subscribe_other = models.TextField("Subscribe Other", blank=True)
|
||||
# Can be 0, 1, -1, or what looks like a person_or_org_tag, positive or neg.
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
from forms import NonWgStep1, ListReqStep1, PickApprover, DeletionPickApprover, UrlMultiWidget, Preview, ListReqAuthorized, ListReqClose, MultiEmailField, AdminRequestor, ApprovalComment
|
||||
from models import NonWgMailingList, MailingList
|
||||
from ietf.idtracker.models import Area, PersonOrOrgInfo
|
||||
from forms import NonWgStep1, ListReqStep1, PickApprover, DeletionPickApprover, UrlMultiWidget, Preview, ListReqAuthorized, ListReqClose, MultiEmailField, AdminRequestor, ApprovalComment, ListApprover
|
||||
from models import NonWgMailingList, MailingList, Domain
|
||||
from ietf.idtracker.models import Area, PersonOrOrgInfo, AreaDirector, WGChair
|
||||
from django import newforms as forms
|
||||
from django.shortcuts import get_object_or_404, render_to_response
|
||||
from django.template import RequestContext
|
||||
|
@ -145,7 +145,6 @@ list_fields = {
|
|||
'add_comment': None,
|
||||
'mail_type': None,
|
||||
'mail_cat': None,
|
||||
'domain_name': None,
|
||||
'admins': MultiEmailField(label='List Administrator(s)', widget=AdminRequestor(attrs={'cols': 41, 'rows': 4})),
|
||||
'initial': MultiEmailField(label='Initial list member(s)', widget=forms.Textarea(attrs={'cols': 41, 'rows': 4}), required=False),
|
||||
}
|
||||
|
@ -160,6 +159,7 @@ list_widgets = {
|
|||
'post_who': forms.Select(choices=(('1', 'List members only'), ('0', 'Open'))),
|
||||
'post_admin': forms.Select(choices=(('0', 'No'), ('1', 'Yes'))),
|
||||
'archive_private': forms.Select(choices=(('0', 'No'), ('1', 'Yes'))),
|
||||
'domain_name': forms.HiddenInput(),
|
||||
}
|
||||
|
||||
list_attrs = {
|
||||
|
@ -177,7 +177,18 @@ list_attrs = {
|
|||
|
||||
list_callback = form_decorator(fields=list_fields, widgets=list_widgets, attrs=list_attrs)
|
||||
|
||||
def gen_list_approval(approvers, requestor, parent):
|
||||
class ListApproval(parent):
|
||||
_approvers = approvers
|
||||
_requestor = requestor
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(ListApproval, self).__init__(self._approvers, self._requestor, *args, **kwargs)
|
||||
return ListApproval
|
||||
|
||||
class ListReqWizard(wizard.Wizard):
|
||||
clean_forms = []
|
||||
main_step = 1
|
||||
requestor_is_approver = False
|
||||
def get_template(self):
|
||||
templates = []
|
||||
#if self.step > 0:
|
||||
|
@ -190,6 +201,12 @@ class ListReqWizard(wizard.Wizard):
|
|||
templates.append("mailinglists/list_wizard.html")
|
||||
print templates
|
||||
return templates
|
||||
def render_template(self, *args, **kwargs):
|
||||
#self.extra_context['clean_forms'] = self.clean_forms
|
||||
if self.step > self.main_step:
|
||||
self.extra_context['main_form'] = self.clean_forms[self.main_step]
|
||||
self.extra_context['requestor_is_approver'] = self.requestor_is_approver
|
||||
return super(ListReqWizard, self).render_template(*args, **kwargs)
|
||||
# want to implement parse_params to get domain for list
|
||||
def process_step(self, request, form, step):
|
||||
form.full_clean()
|
||||
|
@ -197,15 +214,60 @@ class ListReqWizard(wizard.Wizard):
|
|||
self.clean_forms = [ form ]
|
||||
else:
|
||||
self.clean_forms.append(form)
|
||||
form0 = self.clean_forms[0]
|
||||
needs_auth = form0.clean_data['mail_type'].endswith('non') and form0.clean_data['domain_name'] != 'ietf.org'
|
||||
if step == 0:
|
||||
if form.clean_data['mail_type'].endswith('non') and form.clean_data['domain_name'] != 'ietf.org':
|
||||
self.main_step = 1
|
||||
if needs_auth:
|
||||
self.form_list.append(ListReqAuthorized)
|
||||
self.main_step = 2
|
||||
if form.clean_data['mail_type'].startswith('close'):
|
||||
self.form_list.append(ListReqClose)
|
||||
if form.clean_data['mail_type'] == 'closewg':
|
||||
self.initial[self.main_step] = {'mlist_name': form.clean_data['group']}
|
||||
else:
|
||||
self.initial[self.main_step] = {'mlist_name': form.clean_data['list_to_close']}
|
||||
else:
|
||||
self.form_list.append(forms.form_for_model(MailingList, formfield_callback=list_callback))
|
||||
#XXX not quite
|
||||
if form.clean_data['mail_type'].endswith('wg'):
|
||||
self.initial[self.main_step] = {'mlist_name': form.clean_data['group']}
|
||||
else:
|
||||
self.initial[self.main_step] = {}
|
||||
if form.clean_data['mail_type'].endswith('wg'):
|
||||
self.initial[self.main_step].update({'domain_name': 'ietf.org'})
|
||||
else:
|
||||
self.initial[self.main_step].update({'domain_name': form.clean_data['domain_name']})
|
||||
if step == self.main_step:
|
||||
approvers = mlist_approvers(form0.clean_data['mail_type'], form0.clean_data['domain_name'], form0.clean_data['group'])
|
||||
main_form = self.clean_forms[self.main_step]
|
||||
requestor_email = main_form.clean_data['requestor_email']
|
||||
requestor_person = None
|
||||
for a in approvers:
|
||||
if requestor_email == a.person.email()[1]:
|
||||
requestor_person = a
|
||||
self.requestor_is_approver = True
|
||||
self.form_list.append(gen_list_approval(approvers, requestor_person, ListApprover))
|
||||
super(ListReqWizard, self).process_step(request, form, step)
|
||||
def done(self, request, form_list):
|
||||
list = MailingList(**self.clean_forms[self.main_step].clean_data)
|
||||
list.mailing_list_id = None # make sure that we create a new row
|
||||
list.auth_person_id = int(self.clean_forms[self.main_step + 1].clean_data['approver'])
|
||||
list.mail_type = MailingList.MAILTYPE_MAP[self.clean_forms[0].clean_data['mail_type']]
|
||||
list.approved = 0
|
||||
list.save()
|
||||
approver_email = list.auth_person.email()
|
||||
send_mail_subj(request, [ approver_email ], None, 'mailinglists/list_wizard_subject.txt', 'mailinglists/list_wizard_done_email.txt', {'list': list, 'forms': self.clean_forms, 'requestor_is_approver': self.requestor_is_approver})
|
||||
return render_to_response('mailinglists/list_wizard_done.html', {'list': list, 'forms': self.clean_forms, 'requestor_is_approver': self.requestor_is_approver}, context_instance=RequestContext(request) )
|
||||
|
||||
def mlist_approvers(mail_type, domain_name, group):
|
||||
approvers = []
|
||||
if domain_name == 'ietf.org':
|
||||
approvers += AreaDirector.objects.filter(area__status=Area.ACTIVE)
|
||||
if mail_type.endswith('wg'):
|
||||
approvers += WGChair.objects.filter(group_acronym=group)
|
||||
domain = Domain.objects.get(domain=domain_name)
|
||||
approvers += domain.approvers.all()
|
||||
return approvers
|
||||
|
||||
def list_req_wizard(request):
|
||||
wiz = ListReqWizard([ ListReqStep1 ])
|
||||
|
|
|
@ -1,39 +1,5 @@
|
|||
{% extends "mailinglists/list_wizard_base.html" %}
|
||||
|
||||
{% block mlcontent %}
|
||||
<table bgcolor="#88AED2" cellspacing="1" border="0" width="594">
|
||||
<tr><td>
|
||||
<table bgcolor="#f3f8fd" cellpadding="3" cellspacing="0" border="0" width="100%">
|
||||
|
||||
<tr>
|
||||
<td colspan="2"><img src="/images/ietf_topleft.gif" border="0"><img src="/images/blue_title.gif" border="0"></td>
|
||||
</tr>
|
||||
<tr><td colspan="2">
|
||||
<img src="/images/mail_title.gif" border="0">
|
||||
</td></tr>
|
||||
<tr><td colspan="2" valign="top">
|
||||
<img src="/images/t_un.gif" border="0">
|
||||
</td></tr>
|
||||
</table>
|
||||
<form action="." method="POST">
|
||||
|
||||
<h2>Step {{ step|add:"1" }}:</h2>
|
||||
<table bgcolor="f3f8fd" cellpadding="3" cellspacing="0" border="0" width="100%">
|
||||
{% block mlform %}
|
||||
{{ form }}
|
||||
</table>
|
||||
|
||||
|
||||
<input type="hidden" name="{{ step_field }}" value="{{ step }}" />
|
||||
|
||||
{{ previous_fields }}
|
||||
|
||||
<input type="submit" value="Next">
|
||||
</form>
|
||||
|
||||
<!--
|
||||
clean_forms: {{ clean_forms|escape }}
|
||||
-->
|
||||
</td></tr>
|
||||
</table>
|
||||
|
||||
{% endblock %}
|
||||
|
|
32
ietf/templates/mailinglists/list_wizard_ListApproval.html
Normal file
32
ietf/templates/mailinglists/list_wizard_ListApproval.html
Normal file
|
@ -0,0 +1,32 @@
|
|||
{% extends "mailinglists/list_wizard_base.html" %}
|
||||
|
||||
{% block mlform %}
|
||||
<tr>
|
||||
<th colspan=2>Preview</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>{{ main_form.requestor.label }}</th>
|
||||
<td>{{ main_form.requestor.field.data|escape }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>{{ main_form.requestor_email.label }}</th>
|
||||
<td>{{ main_form.requestor_email.field.data|escape }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>{{ main_form.mlist_name.label }}</th>
|
||||
<td>{{ main_form.mlist_name.field.data|escape }}@{{ main_form.domain_name }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>{{ main_form.requestor_email.label }}</th>
|
||||
<td>{{ main_form.requestor_email.field.data|escape }}</td>
|
||||
</tr>
|
||||
<!-- make sure we have all the fields -->
|
||||
{% for field in main_form %}
|
||||
<tr>
|
||||
<th>{{ field.label }}</th>
|
||||
<td>{{ field.data|escape|linebreaksbr }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
<!-- must figure out whether this is an approver or a hidden field -->
|
||||
{{ form }}
|
||||
{% endblock %}
|
|
@ -17,9 +17,43 @@ ul.errorlist { color: red; border: 1px solid red; }
|
|||
<img src="/images/nwg/t_un1.gif" border="0">
|
||||
<!-- form step {{ step }} -->
|
||||
<br>
|
||||
{% block mlcontent %}
|
||||
<table bgcolor="#88AED2" cellspacing="1" border="0" width="594">
|
||||
<tr><td>
|
||||
<table bgcolor="#f3f8fd" cellpadding="3" cellspacing="0" border="0" width="100%">
|
||||
|
||||
<tr>
|
||||
<td colspan="2"><img src="/images/ietf_topleft.gif" border="0"><img src="/images/blue_title.gif" border="0"></td>
|
||||
</tr>
|
||||
<tr><td colspan="2">
|
||||
<img src="/images/mail_title.gif" border="0">
|
||||
</td></tr>
|
||||
<tr><td colspan="2" valign="top">
|
||||
<img src="/images/t_un.gif" border="0">
|
||||
</td></tr>
|
||||
</table>
|
||||
<form action="." method="POST">
|
||||
|
||||
<h2>Step {{ step|add:"1" }}:</h2>
|
||||
<table bgcolor="f3f8fd" cellpadding="3" cellspacing="0" border="0" width="100%">
|
||||
{% block mlform %}
|
||||
form goes here
|
||||
{% endblock %}
|
||||
</table>
|
||||
|
||||
|
||||
<input type="hidden" name="{{ step_field }}" value="{{ step }}" />
|
||||
|
||||
{{ previous_fields }}
|
||||
|
||||
<input type="submit" value="Next">
|
||||
</form>
|
||||
|
||||
<!--
|
||||
clean_forms: {{ clean_forms|escape }}
|
||||
-->
|
||||
</td></tr>
|
||||
</table>
|
||||
|
||||
</blockquote>
|
||||
|
||||
{% endblock %}
|
||||
|
|
25
ietf/templates/mailinglists/list_wizard_done.html
Normal file
25
ietf/templates/mailinglists/list_wizard_done.html
Normal file
|
@ -0,0 +1,25 @@
|
|||
{% extends "mailinglists/list_approval_base.html" %}
|
||||
|
||||
{% block innercontent %}
|
||||
{% if requestor_is_approver %}
|
||||
An email has been sent to you so you can confirm this request.<br>
|
||||
Please note that after your request has been approved by you,<br>
|
||||
{% else %}
|
||||
Your request to {% filter escape %}{% include "mailinglists/list_type_message2.txt" %}{% endfilter %}
|
||||
has been sent to<br>
|
||||
{{ list.auth_person }} for approval.<br>
|
||||
Please note that after your request has been approved by <br>
|
||||
{{ list.auth_person }},
|
||||
{% endif %}
|
||||
{% ifequal req "delete" %}
|
||||
your list will be closed within two business days.<br>
|
||||
{% else %}
|
||||
your list will be created and the archives <br>
|
||||
will be tested.<br>
|
||||
You will receive a welcome E-mail containing your administrator's<br>
|
||||
password within two business days.<br>
|
||||
For security reasons we suggest that you change this password.<br>
|
||||
Please remember to forward this changed password to any other list<br>
|
||||
admins. <br>
|
||||
{% endifequal %}
|
||||
{% endblock %}
|
9
ietf/templates/mailinglists/list_wizard_done_email.txt
Normal file
9
ietf/templates/mailinglists/list_wizard_done_email.txt
Normal file
|
@ -0,0 +1,9 @@
|
|||
{% filter wordwrap:"72" %}
|
||||
The Secretariat has received a request from {{ list.requestor }}
|
||||
to {% include "mailinglists/list_type_message2.txt" %}
|
||||
Please use the following URL to review and approve or deny this request:
|
||||
https://datatracker.ietf.org{% url ietf.mailinglists.views.list_approve list.mailing_list_id %}
|
||||
{% endfilter %}
|
||||
|
||||
{# gotta set req for list_summary to work #}
|
||||
{% include "mailinglists/list_summary.txt" %}
|
1
ietf/templates/mailinglists/list_wizard_subject.txt
Normal file
1
ietf/templates/mailinglists/list_wizard_subject.txt
Normal file
|
@ -0,0 +1 @@
|
|||
{% filter title %}Request to {% include "mailinglists/list_type_message2.txt %}{% endfilter %}
|
Loading…
Reference in a new issue