diff --git a/pandora/0xdb.jsonc b/pandora/0xdb.jsonc index fa5e70560..56c728881 100644 --- a/pandora/0xdb.jsonc +++ b/pandora/0xdb.jsonc @@ -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" }, diff --git a/pandora/padma.jsonc b/pandora/padma.jsonc index 98e1a1fd8..712288306 100644 --- a/pandora/padma.jsonc +++ b/pandora/padma.jsonc @@ -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" }, diff --git a/pandora/user/models.py b/pandora/user/models.py index d940e57e2..a60e44e0b 100644 --- a/pandora/user/models.py +++ b/pandora/user/models.py @@ -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, diff --git a/pandora/user/templates/contact_email.txt b/pandora/user/templates/contact_email.txt index f50c8bfa1..592e71899 100644 --- a/pandora/user/templates/contact_email.txt +++ b/pandora/user/templates/contact_email.txt @@ -3,5 +3,4 @@ Subject: {{subject}} {{message}} --- -{{sitename}} - {{url}} +{{footer}} diff --git a/pandora/user/templates/contact_receipt.txt b/pandora/user/templates/contact_receipt.txt index a2f4b159f..cca54ca40 100644 --- a/pandora/user/templates/contact_receipt.txt +++ b/pandora/user/templates/contact_receipt.txt @@ -4,5 +4,4 @@ Subject: {{subject}} {{message}} --- -{{sitename}} - {{url}} +{{footer}} diff --git a/pandora/user/templates/mailout_receipt.txt b/pandora/user/templates/mailout_receipt.txt new file mode 100644 index 000000000..4517652fa --- /dev/null +++ b/pandora/user/templates/mailout_receipt.txt @@ -0,0 +1,4 @@ +To: {{to|safe}} +Subject: {{subject|safe}} + +{{message|safe}} diff --git a/pandora/user/templates/password_reset_email.txt b/pandora/user/templates/password_reset_email.txt index 1f39ceca3..4603261b1 100644 --- a/pandora/user/templates/password_reset_email.txt +++ b/pandora/user/templates/password_reset_email.txt @@ -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}} diff --git a/pandora/user/views.py b/pandora/user/views.py index ab87dfdf7..499a4d5d3 100644 --- a/pandora/user/views.py +++ b/pandora/user/views.py @@ -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']) diff --git a/static/js/pandora/preferencesDialog.js b/static/js/pandora/preferencesDialog.js index d7d784391..befd2dc55 100644 --- a/static/js/pandora/preferencesDialog.js +++ b/static/js/pandora/preferencesDialog.js @@ -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: ''}); } }) ], diff --git a/static/js/pandora/usersDialog.js b/static/js/pandora/usersDialog.js index 755cb664f..1a7c33e34 100644 --- a/static/js/pandora/usersDialog.js +++ b/static/js/pandora/usersDialog.js @@ -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: $('').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 $('') + .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,135 +261,156 @@ 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 + ' <' + values.email + '>' - }); - $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({}), - - that = Ox.Dialog({ - buttons: [ - Ox.Button({ - title: 'Export E-Mail Addresses' - }) - .css({margin: '4px 4px 4px 0'}) - .bindEvent({ - click: function() { - pandora.api.findUsers({ - query: {conditions: [], operator: '&'}, - keys: ['email', 'username'], - range: [0, numberOfUsers], - sort: [{key: 'username', operator: '+'}] - }, function(result) { - var $dialog = Ox.Dialog({ - buttons: [ - Ox.Button({ - title: 'Close' - }) - .bindEvent({ - click: function() { - $dialog.close(); - } - }) - ], - content: Ox.Element() - .addClass('OxSelectable') - .css({margin: '16px'}) - .html( - result.data.items.filter(function(item) { - return item.email; - }).map(function(item) { - return item.username + ' <' + item.email + '>'; - }).join(', ') - ), - removeOnClose: true, - title: 'E-Mail Addresses' - }) - .open(); - }); - } - }), - Ox.Button({ - id: 'done', - title: 'Done', - width: 48 - }).bindEvent({ - click: function() { - Ox.Request.clearCache('findUsers'); - that.close(); - } - }) - ], - closeButton: true, - content: Ox.SplitPanel({ - elements: [ + $formButton = Ox.ButtonGroup({ + buttons: [ { - element: Ox.SplitPanel({ + 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($guestsCheckbox) - .append($findElement), + .append($formLabel) + .append($formButton), size: 24 }, { - element: $list + element: $form } ], orientation: 'vertical' }) - }, - { - element: Ox.SplitPanel({ - elements: [ - { - element: Ox.Bar({size: 24}) - .append($userLabel), - size: 24 - }, - { - element: $user - } - ], - orientation: 'vertical' + .bindEvent({ + resize: setWidth }), - size: 256 - } - ], - orientation: 'horizontal' - }), - height: height, - maximizeButton: true, - minHeight: 256, - minWidth: 512, - padding: 0, - removeOnClose: true, - title: 'Manage Users', - width: width + resizable: true, + resize: [256, 384, 512], + size: 256 + } + ], + orientation: 'horizontal' }), + that = Ox.Dialog({ + buttons: [ + Ox.Button({ + title: 'Export E-Mail Addresses' + }) + .css({margin: '4px 4px 4px 0'}) + .bindEvent({ + click: function() { + pandora.api.findUsers({ + query: {conditions: [], operator: '&'}, + keys: ['email', 'username'], + range: [0, numberOfUsers], + sort: [{key: 'username', operator: '+'}] + }, function(result) { + var $dialog = Ox.Dialog({ + buttons: [ + Ox.Button({ + title: 'Close' + }) + .bindEvent({ + click: function() { + $dialog.close(); + } + }) + ], + content: Ox.Element() + .addClass('OxSelectable') + .css({margin: '16px'}) + .html( + result.data.items.filter(function(item) { + return item.email; + }).map(function(item) { + return item.username + ' <' + item.email + '>'; + }).join(', ') + ), + removeOnClose: true, + title: 'E-Mail Addresses' + }) + .open(); + }); + } + }), + Ox.Button({ + id: 'done', + title: 'Done', + width: 48 + }).bindEvent({ + click: function() { + Ox.Request.clearCache('findUsers'); + that.close(); + } + }) + ], + closeButton: true, + content: $content, + height: dialogHeight, + maximizeButton: true, + minHeight: 256, + minWidth: 512, + padding: 0, + removeOnClose: true, + title: 'Manage Users', + width: dialogWidth + }) + .bindEvent({ + resize: setHeight + }), + $status = $('