add mail interface to users dialog

This commit is contained in:
rolux 2011-12-18 09:27:15 +00:00
parent 1c40048045
commit bea69a918c
10 changed files with 632 additions and 232 deletions

View file

@ -25,7 +25,8 @@
"canSeeDebugMenu": {"staff": true, "admin": true},
"canSeeFiles": {"staff": true, "admin": true},
"canSeeItem": {"guest": 3, "member": 3, "friend": 4, "staff": 4, "admin": 4},
"canSeeExtraItemViews": {"friend": true, "staff": true, "admin": true}
"canSeeExtraItemViews": {"friend": true, "staff": true, "admin": true},
"canSendMail": {"staff": true, "admin": true}
},
/*
clipKeys are the properties that clips can by sorted by.
@ -549,9 +550,12 @@
],
"sendReferrer": false,
"site": {
// FIXME: "from" and "to" would be more intuitive as keys here
"email": {
// E-mail address in contact form (to)
"contact": "0xdb@0xdb.org",
"footer": "-- \n0xDB - http://0xdb.org",
"prefix": "0xDB Newsletter -",
// E-mail address uses by the system (from)
"system": "0xdb@0xdb.org"
},

View file

@ -461,6 +461,8 @@
"email": {
// E-mail address in contact form (to)
"contact": "pad.ma@pad.ma",
"footer": "-- \nPad.ma - http://pad.ma",
"prefix": "Pad.ma Newsletter -",
// E-mail address uses by the system (from)
"system": "system@pad.ma"
},

View file

@ -135,6 +135,7 @@ class SessionData(models.Model):
'lastseen': self.lastseen,
'level': 'guest',
'location': self.location,
'newsletter': False,
'notes': '',
'numberoflists': 0,
'screensize': self.screensize,
@ -149,6 +150,7 @@ class SessionData(models.Model):
j['disabled'] = not self.user.is_active
j['email'] = self.user.email
j['level'] = p.get_level()
j['newsletter'] = p.newsletter
j['notes'] = p.notes
j['numberoflists'] = self.user.lists.count()
if keys:
@ -263,6 +265,7 @@ def init_user(user, request=None):
result['level'] = profile.get_level()
result['groups'] = [g.name for g in user.groups.all()]
result['email'] = user.email
result['newsletter'] = profile.newsletter
result['ui'] = profile.get_ui()
result['volumes'] = [v.json() for v in user.volumes.all()]
return result
@ -276,6 +279,7 @@ def user_json(user, keys=None):
'id': ox.to26(user.id),
'lastseen': user.last_login,
'level': p.get_level(),
'newsletter': p.newsletter,
'notes': p.notes,
'numberoflists': user.lists.count(),
'username': user.username,

View file

@ -3,5 +3,4 @@ Subject: {{subject}}
{{message}}
--
{{sitename}} - {{url}}
{{footer}}

View file

@ -4,5 +4,4 @@ Subject: {{subject}}
{{message}}
--
{{sitename}} - {{url}}
{{footer}}

View file

@ -0,0 +1,4 @@
To: {{to|safe}}
Subject: {{subject|safe}}
{{message|safe}}

View file

@ -4,5 +4,4 @@ To reset your password, please use the following code:
If you don't want to reset your password, no further action is required.
--
{{sitename}} - {{url}}
{{footer}}

View file

@ -4,12 +4,11 @@ import random
random.seed()
import re
from django.contrib.auth.models import User
from django.contrib.auth import authenticate, login, logout
from django.template import RequestContext, loader
from django.utils import simplejson as json
from django.conf import settings
from django.core.mail import send_mail, BadHeaderError
from django.core.mail import send_mail, BadHeaderError, EmailMessage
from django.db.models import Sum
from django.shortcuts import redirect
@ -280,6 +279,7 @@ def requestToken(request):
context = RequestContext(request, {
'code': code,
'sitename': settings.SITENAME,
'footer': settings.CONFIG['site']['email']['footer'],
'url': request.build_absolute_uri('/'),
})
message = template.render(context)
@ -333,6 +333,8 @@ def editUser(request):
profile.set_level(data['level'])
if 'notes' in data:
profile.notes = data['notes']
if 'newsletter' in data:
profile.newsletter = data['newsletter']
if 'username' in data:
if models.User.objects.filter(username=data['username']).exclude(id=user.id).count()>0:
response = json_response(status=403, text='username already in use')
@ -388,10 +390,10 @@ def findUser(request):
if data['key'] == 'email':
response['data']['users'] = [models.user_json(u, keys)
for u in User.objects.filter(email__iexact=data['value'])]
for u in models.User.objects.filter(email__iexact=data['value'])]
else:
response['data']['users'] = [models.user_json(u, keys)
for u in User.objects.filter(username__iexact=data['value'])]
for u in models.User.objects.filter(username__iexact=data['value'])]
return render_to_json_response(response)
actions.register(findUser)
@ -527,6 +529,71 @@ Positions
return render_to_json_response(response)
actions.register(findUsers)
@login_required_json
def mail(request):
'''
param data {
'to': array of usernames,
'subject': string,
'message': string
}
message can contain {username} or {email},
this will be replace with the user/email
the mail is sent to.
return {
'status': {'code': int, 'text': string}
}
'''
response = json_response()
data = json.loads(request.POST['data'])
p = request.user.get_profile()
if p.capability('canSendMail'):
email_from = '"%s" <%s>' % (settings.SITENAME, settings.CONFIG['site']['email']['system'])
headers = {
'Reply-To': settings.CONFIG['site']['email']['contact']
}
subject = data.get('subject', '').strip()
users = [models.User.objects.get(username=username) for username in data['to']]
for user in users:
if user.email:
message = data['message']
for key, value in (
('{username}', user.username),
('{email}', user.email),
):
message = message.replace(key, value)
email_to = '"%s" <%s>' % (user.username, user.email)
email = EmailMessage(subject,
message,
email_from,
[email_to],
headers = headers)
email.send(fail_silently=True)
if 'receipt' in data \
and data['receipt']:
template = loader.get_template('mailout_receipt.txt')
context = RequestContext(request, {
'footer': settings.CONFIG['site']['email']['footer'],
'to': ', '.join(['"%s" <%s>' % (u.username, u.email) for u in users]),
'subject': subject,
'message': data['message'],
'url': request.build_absolute_uri('/'),
})
message = template.render(context)
subject = u'Fwd: %s' % subject
email_to = '"%s" <%s>' % (request.user.username, request.user.email)
receipt = EmailMessage(subject,
message,
email_from,
[email_to])
receipt.send(fail_silently=True)
response = json_response(text='message sent')
else:
response = json_response(status=403, text='not allowed to send mail')
return render_to_json_response(response)
actions.register(mail, cache=False)
def contact(request):
'''
@ -549,7 +616,7 @@ def contact(request):
if not email:
email = request.user.email
if 'message' in data and data['message'].strip():
email_from = settings.CONFIG['site']['email']['system']
email_from = '"%s" <%s>' % (settings.SITENAME, settings.CONFIG['site']['email']['system'])
email_to = [settings.CONFIG['site']['email']['contact'], ]
subject = data.get('subject', '').strip()
template = loader.get_template('contact_email.txt')
@ -559,12 +626,13 @@ def contact(request):
'subject': subject,
'message': data['message'].strip(),
'sitename': settings.SITENAME,
'footer': settings.CONFIG['site']['email']['footer'],
'url': request.build_absolute_uri('/'),
})
message = template.render(context)
response = json_response(text='message sent')
try:
send_mail(u'[%s Contact] %s' % (settings.SITENAME, subject), message, email_from, email_to)
send_mail(u'%s Contact - %s' % (settings.SITENAME, subject), message, email_from, email_to)
except BadHeaderError:
response = json_response(status=400, text='invalid data')
if request.user.is_authenticated() \
@ -575,6 +643,7 @@ def contact(request):
'name': name,
'from': email,
'sitename': settings.SITENAME,
'footer': settings.CONFIG['site']['email']['footer'],
'to': email_to[0],
'subject': subject,
'message': data['message'].strip(),
@ -618,6 +687,10 @@ def editPreferences(request):
else:
change = True
request.user.email = data['email']
if 'newsletter' in data:
profile = request.user.get_profile()
profile.newsletter = data['newsletter']
profile.save()
if 'password' in data:
change = True
request.user.set_password(data['password'])

View file

@ -83,6 +83,28 @@ pandora.ui.preferencesDialog = function() {
})
);
} else {
$content.append(
Ox.Checkbox({
checked: pandora.user.newsletter,
id: 'newsletter',
label: 'Newsletter',
labelWidth: 80,
title: pandora.user.newsletter ? 'Subscribed' : 'Unsubscribed',
width: 240
})
.bindEvent({
change: function(data) {
this.options({
title: this.options('title') == 'Subscribed' ? 'Unsubscribed' : 'Subscribed'
});
pandora.user.newsletter = data.checked;
pandora.api.editPreferences({
newsletter: pandora.user.newsletter
});
}
})
.css({position: 'absolute', left: '96px', top: '16px'})
);
$content.append(
Ox.Button({
title: 'Reset UI Settings',
@ -94,26 +116,8 @@ pandora.ui.preferencesDialog = function() {
pandora.$ui.appPanel.reload();
}
})
.css({position: 'absolute', left: '96px', top: '16px'})
.css({position: 'absolute', left: '96px', top: '46px'})
);
/*
content.append(Ox.FormElementGroup({
elements: [
Ox.Checkbox({
checked: true ,
id: 'showEpisodes',
title: 'Show Episodes',
width: 320
}),
Ox.Checkbox({
checked: true ,
id: 'newsletter',
title: 'Receive Newsletter',
width: 320
})
]
}));
*/
}
return $content;
},
@ -127,7 +131,7 @@ pandora.ui.preferencesDialog = function() {
}).bindEvent({
click: function() {
$dialog.close();
pandora.URL.update();
pandora.UI.set({page: ''});
}
})
],

View file

@ -4,8 +4,9 @@
pandora.ui.usersDialog = function() {
var height = Math.round((window.innerHeight - 48) * 0.9),
width = Math.round(window.innerWidth * 0.9),
var dialogHeight = Math.round((window.innerHeight - 48) * 0.9),
dialogWidth = Math.round(window.innerWidth * 0.9),
formWidth = 256,
numberOfUsers = 0,
userLevels = ['member', 'friend', 'staff', 'admin'],
@ -77,9 +78,8 @@ pandora.ui.usersDialog = function() {
},
id: 'disabled',
operator: '-',
title: $('<img>').attr({
src: Ox.UI.getImageURL('symbolCheck')
}),
title: 'Enabled',
titleImage: 'check',
visible: true,
width: 16
},
@ -126,6 +126,26 @@ pandora.ui.usersDialog = function() {
visible: true,
width: 60
},
{
format: function(value) {
return $('<img>')
.attr({
src: Ox.UI.getImageURL('symbolMail')
})
.css({
width: '10px',
height: '10px',
padding: '3px',
opacity: +value
});
},
id: 'newsletter',
title: 'Newsletter',
titleImage: 'mail',
operator: '-',
visible: true,
width: 16
},
{
id: 'numberoflists',
align: 'right',
@ -222,7 +242,7 @@ pandora.ui.usersDialog = function() {
columnsVisible: true,
items: pandora.api.findUsers,
keys: ['notes'],
max: 1,
max: -1,
scrollbarVisible: true,
sort: [{key: 'lastseen', operator: '-'}]
})
@ -241,33 +261,87 @@ pandora.ui.usersDialog = function() {
)
);
},
select: function(data) {
var values;
$user.empty();
if (data.ids.length) {
values = $list.value(data.ids[0]);
if(values.level != 'guest') {
$userLabel.options({
title: values.username + ' &lt;' + values.email + '&gt;'
});
$user.append(renderUserForm(values));
} else {
$userLabel.options({title: 'Guest'});
}
} else {
$userLabel.options({title: 'No user selected'});
}
}
select: selectUsers
}),
$userLabel = Ox.Label({
$formLabel = Ox.Label({
textAlign: 'center',
title: 'No user selected',
width: 248
width: 212
})
.css({margin: '4px'}),
.css({float: 'left', margin: '4px 2px 4px 4px'}),
$user = Ox.Element({}),
$formButton = Ox.ButtonGroup({
buttons: [
{
id: 'edit',
selected: true,
title: 'edit',
tooltip: 'Edit'
},
{
id: 'mail',
title: 'mail',
tooltip: 'Mail'
}
],
selectable: true,
type: 'image'
})
.css({float: 'left', margin: '4px 4px 4px 2px'})
.bindEvent({
change: selectForm
}),
$form = Ox.Element({}),
$editForm,
$mailForm = renderMailForm(),
$content = Ox.SplitPanel({
elements: [
{
element: Ox.SplitPanel({
elements: [
{
element: Ox.Bar({size: 24})
.append($guestsCheckbox)
.append($findElement),
size: 24
},
{
element: $list
}
],
orientation: 'vertical'
})
},
{
element: Ox.SplitPanel({
elements: [
{
element: Ox.Bar({size: 24})
.append($formLabel)
.append($formButton),
size: 24
},
{
element: $form
}
],
orientation: 'vertical'
})
.bindEvent({
resize: setWidth
}),
resizable: true,
resize: [256, 384, 512],
size: 256
}
],
orientation: 'horizontal'
}),
that = Ox.Dialog({
buttons: [
@ -323,51 +397,18 @@ pandora.ui.usersDialog = function() {
})
],
closeButton: true,
content: Ox.SplitPanel({
elements: [
{
element: Ox.SplitPanel({
elements: [
{
element: Ox.Bar({size: 24})
.append($guestsCheckbox)
.append($findElement),
size: 24
},
{
element: $list
}
],
orientation: 'vertical'
})
},
{
element: Ox.SplitPanel({
elements: [
{
element: Ox.Bar({size: 24})
.append($userLabel),
size: 24
},
{
element: $user
}
],
orientation: 'vertical'
}),
size: 256
}
],
orientation: 'horizontal'
}),
height: height,
content: $content,
height: dialogHeight,
maximizeButton: true,
minHeight: 256,
minWidth: 512,
padding: 0,
removeOnClose: true,
title: 'Manage Users',
width: width
width: dialogWidth
})
.bindEvent({
resize: setHeight
}),
$status = $('<div>')
@ -383,31 +424,57 @@ pandora.ui.usersDialog = function() {
})
.appendTo(that.$element.find('.OxButtonsbar'));
function getFormItemById(id) {
var ret;
Ox.forEach((
$formButton.value() == 'edit' ? $editForm : $mailForm
).options('items'), function($item) {
if ($item.options('id') == id) {
ret = $item;
return false;
}
});
return ret;
};
function renderUserForm(userData) {
var $checkbox;
function getTo() {
return $list.options('selected').map(function(id) {
return $list.value(id);
}).filter(function(user) {
return user.level != 'guest' && (
$mailForm.values().include == 'users' || user.newsletter
);
}).map(function(user) {
return user.username;
});
}
function renderEditForm() {
var user = $list.value($list.options('selected')[0]);
return Ox.Form({
items: [
$checkbox = Ox.Checkbox({
checked: !userData.disabled,
Ox.Checkbox({
checked: !user.disabled,
id: 'status',
label: 'Status',
labelWidth: 80,
title: 'Enabled',
width: 240
title: !user.disabled ? 'Enabled' : 'Disabled',
width: formWidth - 16
})
.bindEvent({
change: function(data) {
// fixme: it would be really nice to have "this" here
$checkbox.options({title: userData.checked ? 'Enabled' : 'Disabled'})
this.options({
title: this.options('title') == 'Enabled'
? 'Disabled' : 'Enabled'
});
}
}),
Ox.Input({
id: 'username',
label: 'Username',
labelWidth: 80,
value: userData.username,
width: 240
value: user.username,
width: formWidth - 16
})
.bindEvent({
submit: function(data) {
@ -418,8 +485,8 @@ pandora.ui.usersDialog = function() {
id: 'email',
label: 'E-Mail',
labelWidth: 80,
value: userData.email,
width: 240
value: user.email,
width: formWidth - 16
})
.bindEvent({
submit: function(data) {
@ -430,27 +497,38 @@ pandora.ui.usersDialog = function() {
id: 'level',
items: userLevels.map(function(level) {
return {
checked: level == userData.level,
checked: level == user.level,
id: level,
title: Ox.toTitleCase(level)
};
}),
label: 'Level',
labelWidth: 80,
width: 240
width: formWidth - 16
}),
/*
Ox.Label({
title: 'Notes'
Ox.Checkbox({
checked: user.newsletter,
id: 'newsletter',
label: 'Newsletter',
labelWidth: 80,
title: user.newsletter ? 'Subscribed' : 'Unsubscribed',
width: formWidth - 16
})
.bindEvent({
change: function(data) {
this.options({
title: this.options('title') == 'Subscribed'
? 'Unsubscribed' : 'Subscribed'
});
}
}),
*/
Ox.Input({
height: 120,
height: dialogHeight - 160,
id: 'notes',
placeholder: 'Notes',
type: 'textarea',
value: userData.notes,
width: 240
value: user.notes,
width: formWidth - 16
})
],
width: 240
@ -458,15 +536,17 @@ pandora.ui.usersDialog = function() {
.css({margin: '8px'})
.bindEvent({
change: function(event) {
var data = {id: userData.id}, key, value;
var data = {id: user.id}, key, value;
if (event.id == 'status') {
data.disabled = !event.data.checked;
} else if (event.id == 'level') {
data.level = event.data.selected[0].id;
} else if (event.id == 'newsletter') {
data.newsletter = event.data.checked;
} else {
data[event.id] = event.data.value;
}
$list.value(userData.id, event.id, data[event.id]);
$list.value(user.id, event.id, data[event.id]);
pandora.api.editUser(data, function(result) {
Ox.Request.clearCache('findUsers');
});
@ -474,6 +554,238 @@ pandora.ui.usersDialog = function() {
});
}
function renderMailForm() {
return Ox.Form({
items: [
Ox.Input({
disabled: true,
id: 'from',
label: 'From',
labelWidth: 80,
value: pandora.site.site.name + ' <' + pandora.site.site.email.contact + '>',
width: formWidth - 16
}),
Ox.Input({
disabled: true,
id: 'to',
label: 'To',
labelWidth: 80,
value: '',
width: formWidth - 16
}),
Ox.Select({
id: 'include',
items: [
{id: 'users', title: 'All users'},
{id: 'subscribers', title: 'Subscribers only'},
],
label: 'Include',
labelWidth: 80,
width: formWidth - 16
})
.bindEvent({
change: function() {
setTo();
setSend();
}
}),
Ox.Input({
id: 'subject',
label: 'Subject',
labelWidth: 80,
value: pandora.site.site.email.prefix + ' ',
width: formWidth - 16
})
.bindEvent({
change: setSend
}),
Ox.Input({
height: dialogHeight - 208,
id: 'message',
placeholder: 'Message',
type: 'textarea',
value: '\n\n' + pandora.site.site.email.footer,
width: formWidth - 16
})
.bindEvent({
change: setSend
}),
Ox.Select({
id: 'insert',
items: [
{id: 'username', title: 'Username'},
{id: 'email', title: 'E-Mail address'},
],
selectable: false,
title: 'Insert...',
width: formWidth - 16
})
.bindEvent({
click: function(data) {
var $input = getFormItemById('message'),
textarea = $input.find('textarea')[0],
value = $input.options('value');
$input.options({
value: value.substr(0, textarea.selectionStart)
+ '{' + data.id + '}'
+ value.substr(textarea.selectionEnd)
})
.focusInput(textarea.selectionStart + data.id.length + 2);
}
}),
Ox.Checkbox({
checked: false,
id: 'receipt',
title: 'Send a receipt to ' + pandora.user.email,
width: formWidth - 16
}),
Ox.Button({
disabled: true,
id: 'send',
title: 'Send',
width: 64
})
.bindEvent({
click: sendMail
})
],
width: formWidth - 16
})
.css({margin: '8px', textAlign: 'right'});
}
function selectForm(data) {
var selected;
if (data.selected[0] == 'edit') {
$mailForm.detach();
selected = $list.options('selected');
if (selected.length == 1 && selected[0].level != 'guest') {
$form.append($editForm = renderEditForm());
}
} else {
setTo();
setSend();
setWidth();
$editForm && $editForm.remove();
$form.append($mailForm);
}
}
function selectUsers(data) {
var users = $list.options('selected').map(function(id) {
return $list.value(id);
});
setLabel();
if ($formButton.value() == 'edit') {
$form.empty();
if (data.ids.length == 1) {
if (users[0].level != 'guest') {
$form.append($editForm = renderEditForm());
}
}
} else {
setTo();
setSend();
}
}
function sendMail() {
pandora.api.mail({
to: getTo(),
subject: getFormItemById('subject').options('value'),
message: getFormItemById('message').options('value'),
receipt: getFormItemById('receipt').value()
}, function(result) {
var title = result.status.code == 200
? 'Message Sent'
: 'Application Error',
message = result.status.code == 200
? 'Your message has been sent.'
: 'Your message could not be sent. Please try again.',
$dialog = Ox.Dialog({
buttons: [
Ox.Button({
id: 'close',
title: 'Close'
})
.bindEvent({
click: function() {
$dialog.close();
}
})
],
// FIXME: we need a template for this type of dialog
content: Ox.Element()
.append(
$('<img>')
.attr({src: '/static/png/icon64.png'})
.css({position: 'absolute', left: '16px', top: '16px', width: '64px', height: '64px'})
)
.append(
$('<div>')
.css({position: 'absolute', left: '96px', top: '16px', width: '192px'})
.html(message)
),
height: 128,
keys: {enter: 'close', escape: 'close'},
removeOnClose: true,
title: title,
width: 304
}).open();
});
}
function setHeight(data) {
var form = $formButton.options('value'),
$item = getFormItemById(form == 'edit' ? 'notes' : 'message');
Ox.print('$ITEM', $item)
dialogHeight = data.height;
$item && $item.options({
height: dialogHeight - (form == 'edit' ? 160 : 208)
});
}
function setLabel() {
var users = $list.options('selected').map(function(id) {
return $list.value(id);
}),
title = users.length == 0 ? 'No user selected'
: users.length == 1 ? (
users[0].level == 'guest'
? 'Guest'
: users[0].username + ' &lt;' + users[0].email + '&gt;'
)
: users.length + ' users selected';
$formLabel.options({title: title});
}
function setSend() {
getFormItemById('send').options({
disabled: getFormItemById('to').options('value') == 'No recipients'
|| getFormItemById('subject').options('value') === ''
|| getFormItemById('message').options('value') === ''
});
}
function setTo() {
var recipients = getTo().length;
$mailForm.values({
to: (recipients || 'No') + ' recipient' + (recipients == 1 ? '' : 's')
});
}
function setWidth() {
formWidth = $content.size(1);
$formLabel.options({width: formWidth - 44});
(
$formButton.value() == 'edit' ? $editForm : $mailForm
).options('items').forEach(function($item) {
if ($item.options('id') != 'send') {
$item.options({width: formWidth - 16});
}
});
}
function updateList() {
var guests = $guestsCheckbox.value(),
key = $findSelect.value(),