Ralfp 12 лет назад
Родитель
Сommit
464dedd828

+ 23 - 3
misago/users/admin/__init__.py

@@ -3,7 +3,7 @@ from django.utils.translation import ugettext_lazy as _
 from misago.admin import AdminSection, AdminAction
 from misago.admin import AdminSection, AdminAction
 from misago.acl.models import Role
 from misago.acl.models import Role
 from misago.banning.models import Ban
 from misago.banning.models import Ban
-from misago.users.models import User, Rank
+from misago.users.models import User, Rank, Newsletter
 
 
 ADMIN_SECTIONS=(
 ADMIN_SECTIONS=(
     AdminSection(
     AdminSection(
@@ -152,9 +152,29 @@ ADMIN_ACTIONS=(
                name=_("Newsletters"),
                name=_("Newsletters"),
                help=_("Manage and send Newsletters"),
                help=_("Manage and send Newsletters"),
                icon='envelope',
                icon='envelope',
+               model=Newsletter,
+               actions=[
+                        {
+                         'id': 'list',
+                         'name': _("Browse Newsletters"),
+                         'help': _("Browse all existing Newsletters"),
+                         'route': 'admin_users_newsletters'
+                         },
+                        {
+                         'id': 'new',
+                         'name': _("New Newsletter"),
+                         'help': _("Create new Newsletter"),
+                         'route': 'admin_users_newsletters_new'
+                         },
+                        ],
                route='admin_users_newsletters',
                route='admin_users_newsletters',
-               urlpatterns=patterns('misago.admin.views',
-                        url(r'^$', 'todo', name='admin_users_newsletters'),
+               urlpatterns=patterns('misago.users.admin.newsletters.views',
+                        url(r'^$', 'List', name='admin_users_newsletters'),
+                        url(r'^(?P<page>\d+)/$', 'List', name='admin_users_newsletters'),
+                        url(r'^new/$', 'New', name='admin_users_newsletters_new'),
+                        url(r'^send/(?P<target>\d+)/(?P<token>[a-zA-Z0-9]+)/$', 'send', name='admin_users_newsletters_send'),
+                        url(r'^edit/(?P<target>\d+)/$', 'Edit', name='admin_users_newsletters_edit'),
+                        url(r'^delete/(?P<target>\d+)/$', 'Delete', name='admin_users_newsletters_delete'),
                     ),
                     ),
                ),
                ),
 )
 )

+ 51 - 0
misago/users/admin/newsletters/forms.py

@@ -0,0 +1,51 @@
+from django.core.validators import RegexValidator
+from django.utils.translation import ugettext_lazy as _
+from django import forms
+from misago.forms import Form, YesNoSwitch
+from misago.users.models import Rank
+
+class NewsletterForm(Form):
+    name = forms.CharField(max_length=255)
+    step_size = forms.IntegerField(initial=300,min_value=1)
+    content_html = forms.CharField(widget=forms.Textarea)
+    content_plain = forms.CharField(widget=forms.Textarea)
+    ignore_subscriptions = forms.BooleanField(widget=YesNoSwitch,required=False) 
+    ranks = forms.ModelMultipleChoiceField(widget=forms.CheckboxSelectMultiple,queryset=Rank.objects.order_by('name').all(),required=False)
+    
+    layout = (
+              (
+               _("Newsletter Options"),
+               (
+                ('name', {'label': _("Newsletter Name"), 'help_text': _("Newsletter name will be used as message subject in e-mails sent to members.")}),
+                ('step_size', {'label': _("Step Size"), 'help_text': _("Number of users that message will be sent to before forum refreshes page displaying sending progress.")}),
+                ('ranks', {'label': _("Limit to roles"), 'help_text': _("You can limit this newsletter only to members who have specific ranks. If you dont set any ranks, this newsletter will be sent to every user.")}),
+                ('ignore_subscriptions', {'label': _("Ignore members preferences"), 'help_text': _("Change this option to yes if you want to send this newsletter to members that don't want to receive newsletters. This is good for emergencies.")}),
+               )
+              ),
+              (
+               _("Message"),
+               (
+                ('content_html', {'label': _("HTML Message"), 'help_text': _("HTML message visible to members who can read HTML e-mails."), 'attrs': {'rows': 10}}),
+                ('content_plain', {'label': _("Plain Text Message"), 'help_text': _("Alternative plain text message that will be visible to members that can't or dont want to read HTML e-mails."), 'attrs': {'rows': 10}}),
+               )
+              ),
+             )
+
+
+class SearchNewslettersForm(Form):    
+    name = forms.CharField(max_length=255, required=False)
+    contains = forms.CharField(max_length=255, required=False)
+    type = forms.MultipleChoiceField(widget=forms.CheckboxSelectMultiple, choices=(('0', _("Only to subscribers")), ('1', _("To every member"))), required=False)
+    rank = forms.ModelMultipleChoiceField(widget=forms.CheckboxSelectMultiple, queryset=Rank.objects.order_by('order').all(), required=False)
+    
+    layout = (
+              (
+               _("Search Newsletters"),
+               (
+                ('name', {'label': _("Newsletter Name"), 'attrs': {'placeholder': _("Name contains...")}}),
+                ('contains', {'label': _("Message Contents"), 'attrs': {'placeholder': _("Message contains...")}}),
+                ('type', {'label': _("Newsletter Type")}),
+                ('rank', {'label': _("Recipient Rank")}),
+               ),
+              ),
+             )

+ 203 - 0
misago/users/admin/newsletters/views.py

@@ -0,0 +1,203 @@
+import re
+from django.conf import settings
+from django.core.urlresolvers import reverse as django_reverse
+from django import forms
+from django.db.models import Q
+from django.shortcuts import redirect
+from django.template import RequestContext
+from django.utils.translation import ugettext as _
+from misago.admin import site
+from misago.admin.widgets import *
+from misago.forms import Form
+from misago.users.admin.newsletters.forms import NewsletterForm, SearchNewslettersForm
+from misago.users.models import User, Newsletter
+
+def reverse(route, target=None):
+    if target:
+        if route == 'admin_users_newsletters_send':
+          return django_reverse(route, kwargs={'target': target.pk, 'token': target.token})
+        return django_reverse(route, kwargs={'target': target.pk})
+    return django_reverse(route)
+
+"""
+Views
+"""
+class List(ListWidget):
+    admin = site.get_action('newsletters')
+    id = 'list'
+    columns=(
+             ('newsletter', _("Newsletter")),
+             )
+    nothing_checked_message = _('You have to check at least one newsletter.')
+    actions=(
+             ('delete', _("Delete selected newsletters"), _("Are you sure you want to delete selected newsletters?")),
+             )
+    pagination = 20
+    search_form = SearchNewslettersForm
+    
+    def sort_items(self, request, page_items, sorting_method):
+        return page_items.order_by('-id')
+    
+    def set_filters(self, model, filters):
+        if 'rank' in filters:
+            model = model.filter(ranks__in=filters['rank']).distinct()
+        if 'type' in filters:
+            model = model.filter(ignore_subscriptions__in=[int(x) for x in filters['type']])
+        if 'name' in filters:
+            model = model.filter(name__icontains=filters['name'])
+        if 'content' in filters:
+            model = model.filter(Q(content_html__icontains=filters['content']) | Q(content_plain__icontains=filters['content']))
+        return model
+    
+    def get_item_actions(self, request, item):
+        return (
+                self.action('envelope', _("Send Newsletter"), reverse('admin_users_newsletters_send', item)),
+                self.action('pencil', _("Edit Newsletter"), reverse('admin_users_newsletters_edit', item)),
+                self.action('remove', _("Delete Newsletter"), reverse('admin_users_newsletters_delete', item), post=True, prompt=_("Are you sure you want to delete this newsletter?")),
+                )
+
+    def action_delete(self, request, items, checked):
+        Newsletter.objects.filter(id__in=checked).delete()
+        return Message(_('Selected newsletters have been deleted successfully.'), 'success'), reverse('admin_users_newsletters')
+
+
+class New(FormWidget):
+    admin = site.get_action('newsletters')
+    id = 'new'
+    fallback = 'admin_users_newsletters' 
+    form = NewsletterForm
+    submit_button = _("Save Newsletter")
+    tabbed = True
+        
+    def get_new_url(self, request, model):
+        return reverse('admin_users_newsletters')
+    
+    def get_edit_url(self, request, model):
+        return reverse('admin_users_newsletters_edit', model)
+    
+    def submit_form(self, request, form, target):
+        new_newsletter = Newsletter(
+                      name = form.cleaned_data['name'],
+                      step_size = form.cleaned_data['step_size'],
+                      content_html = form.cleaned_data['content_html'],
+                      content_plain = form.cleaned_data['content_plain'],
+                      ignore_subscriptions = form.cleaned_data['ignore_subscriptions'],
+                     )
+        new_newsletter.generate_token()
+        new_newsletter.save(force_insert=True)
+        
+        for rank in form.cleaned_data['ranks']:
+            new_newsletter.ranks.add(rank)
+        new_newsletter.save(force_update=True)
+        
+        return new_newsletter, Message(_('New Newsletter has been created.'), 'success')
+    
+   
+class Edit(FormWidget):
+    admin = site.get_action('newsletters')
+    id = 'edit'
+    name = _("Edit Newsletter")
+    fallback = 'admin_users_newsletters'
+    form = NewsletterForm
+    target_name = 'name'
+    notfound_message = _('Requested Newsletter could not be found.')
+    submit_fallback = True
+    tabbed = True
+    
+    def get_url(self, request, model):
+        return reverse('admin_users_newsletters_edit', model)
+    
+    def get_edit_url(self, request, model):
+        return self.get_url(request, model)
+    
+    def get_initial_data(self, request, model):
+        return {
+                'name': model.name,
+                'step_size': model.step_size,
+                'ignore_subscriptions': model.ignore_subscriptions,
+                'content_html': model.content_html,
+                'content_plain': model.content_plain,
+                'ranks': model.ranks.all(),
+                }
+    
+    def submit_form(self, request, form, target):
+        target.name = form.cleaned_data['name']
+        target.step_size = form.cleaned_data['step_size']
+        target.ignore_subscriptions = form.cleaned_data['ignore_subscriptions']
+        target.content_html = form.cleaned_data['content_html']
+        target.content_plain = form.cleaned_data['content_plain']
+        target.generate_token()
+        
+        target.ranks.clear()
+        for rank in form.cleaned_data['ranks']:
+            target.ranks.add(rank)
+            
+        target.save(force_update=True)
+        return target, Message(_('Changes in newsletter "%(name)s" have been saved.') % {'name': self.original_name}, 'success')
+
+
+class Delete(ButtonWidget):
+    admin = site.get_action('newsletters')
+    id = 'delete'
+    fallback = 'admin_users_newsletters'
+    notfound_message = _('Requested newsletter could not be found.')
+    
+    def action(self, request, target):
+        target.delete()
+        return Message(_('Newsletter "%(name)s"" has been deleted.') % {'name': target.name}, 'success'), False
+    
+
+def send(request, target, token):
+    try:
+        newsletter = Newsletter.objects.get(pk=target, token=token)
+        
+        # Build recipients selector
+        recipients = User.objects
+        if newsletter.ranks.all():
+            recipients.filter(rank__in=[x.pk for x in newsletter.ranks.all()])
+        if not newsletter.ignore_subscriptions:
+            recipients.filter(receive_newsletters=1)
+        
+        recipients_total = recipients
+        recipients_total = recipients_total.count()
+        if recipients_total < 1:
+            request.messages.set_flash(Message(_('No recipients for newsletter "%(newsletter)s" could be found.') % {'newsletter': newsletter.name}), 'error', 'newsletters')
+            return redirect(reverse('admin_users_newsletters'))
+       
+        for user in recipients.all()[newsletter.progress:(newsletter.progress + newsletter.step_size)]:
+            tokens = {
+              '{{ board_name }}': request.settings.board_name,
+              '{{ username }}': user.username,
+              '{{ user_url }}': django_reverse('user', kwargs={'username': user.username_slug, 'user': user.pk}),
+              '{{ board_url }}': settings.BOARD_ADDRESS,
+            }
+            subject = newsletter.parse_name(tokens)
+            user.email_user(request, 'users/newsletter', subject, {
+                                                                'newsletter': newsletter,
+                                                                'subject': subject,
+                                                                'content_html': newsletter.parse_html(tokens),
+                                                                'content_plain': newsletter.parse_plain(tokens),
+                                                                })
+            newsletter.progress += 1
+        newsletter.generate_token()
+        newsletter.save(force_update=True)
+        
+        if newsletter.progress >= recipients_total:
+            newsletter.progress = 0
+            newsletter.save(force_update=True)
+            request.messages.set_flash(Message(_('Newsletter "%(newsletter)s" has been sent.') % {'newsletter': newsletter.name}), 'success', 'newsletters')
+            return redirect(reverse('admin_users_newsletters'))
+        
+        # Render Progress
+        response = request.theme.render_to_response('processing.html', {
+                'task_name': _('Sending Newsletter'),
+                'target_name': newsletter.name,
+                'message': _('Sent to %(progress)s from %(total)s users') % {'progress': newsletter.progress, 'total': recipients_total},
+                'progress': newsletter.progress * 100 / recipients_total,
+                'cancel_url': reverse('admin_users_newsletters'),
+            }, context_instance=RequestContext(request));
+        response['refresh'] = '2;url=%s' % reverse('admin_users_newsletters_send', newsletter)
+        return response
+    except Newsletter.DoesNotExist:
+        request.messages.set_flash(Message(_('Requested Newsletter could not be found.')), 'error', 'newsletters')
+        return redirect(reverse('admin_users_newsletters'))

+ 1 - 1
misago/users/admin/users/forms.py

@@ -17,7 +17,7 @@ class UserForm(Form):
     new_password = forms.CharField(max_length=255,required=False,widget=forms.PasswordInput)
     new_password = forms.CharField(max_length=255,required=False,widget=forms.PasswordInput)
     signature = forms.CharField(widget=forms.Textarea,required=False)
     signature = forms.CharField(widget=forms.Textarea,required=False)
     avatar_custom = forms.CharField(max_length=255,required=False)
     avatar_custom = forms.CharField(max_length=255,required=False)
-    avatar_ban = forms.BooleanField(widget=YesNoSwitch,required=False) 
+    avatar_ban = forms.BooleanField(widget=YesNoSwitch,required=False)
     avatar_ban_reason_user = forms.CharField(widget=forms.Textarea,required=False)
     avatar_ban_reason_user = forms.CharField(widget=forms.Textarea,required=False)
     avatar_ban_reason_admin = forms.CharField(widget=forms.Textarea,required=False)
     avatar_ban_reason_admin = forms.CharField(widget=forms.Textarea,required=False)
     signature_ban = forms.BooleanField(widget=YesNoSwitch,required=False)
     signature_ban = forms.BooleanField(widget=YesNoSwitch,required=False)

+ 23 - 1
misago/users/models.py

@@ -524,10 +524,32 @@ class Newsletter(models.Model):
     Newsletter
     Newsletter
     """
     """
     name = models.CharField(max_length=255)
     name = models.CharField(max_length=255)
+    token = models.CharField(max_length=32)
     step_size = models.PositiveIntegerField(default=0)
     step_size = models.PositiveIntegerField(default=0)
     progress = models.PositiveIntegerField(default=0)
     progress = models.PositiveIntegerField(default=0)
     content_html = models.TextField(null=True,blank=True)
     content_html = models.TextField(null=True,blank=True)
     content_plain = models.TextField(null=True,blank=True)
     content_plain = models.TextField(null=True,blank=True)
     ignore_subscriptions = models.BooleanField(default=False)
     ignore_subscriptions = models.BooleanField(default=False)
-    roles = models.ManyToManyField(Role)
+    ranks = models.ManyToManyField(Rank)
+    
+    def generate_token(self):
+        self.token = get_random_string(32)
+    
+    def parse_name(self, tokens):
+        name = self.name
+        for key in tokens:
+            name = name.replace(key, tokens[key])
+        return name
+    
+    def parse_html(self, tokens):
+        content_html = self.content_html
+        for key in tokens:
+            content_html = content_html.replace(key, tokens[key])
+        return content_html
+    
+    def parse_plain(self, tokens):
+        content_plain = self.content_plain
+        for key in tokens:
+            content_plain = content_plain.replace(key, tokens[key])
+        return content_plain
     
     

+ 9 - 0
templates/_email/users/newsletter_html.html

@@ -0,0 +1,9 @@
+{% extends "_email/base_html.html" %}
+{% load i18n %}
+{% load url from future %}
+
+{% block title %}{{ subject }}{% endblock %}
+
+{% block content %}
+{{ content_html|safe }}
+{% endblock %}

+ 9 - 0
templates/_email/users/newsletter_plain.html

@@ -0,0 +1,9 @@
+{% extends "_email/base_plain.html" %}
+{% load i18n %}
+{% load url from future %}
+
+{% block title %}{{ subject }}{% endblock %}
+
+{% block content %}
+{{ content_plain }}
+{% endblock %}

+ 24 - 0
templates/admin/processing.html

@@ -0,0 +1,24 @@
+{% extends "admin/layout_compact.html" %}
+{% load i18n %}
+{% load url from future %}
+{% import "_forms.html" as form_theme with context %}
+{% from "admin/macros.html" import page_title, draw_message_icon %}
+
+{% block title %}{{ page_title(title=target_name,parent=task_name) }}{% endblock %}
+
+{% block header %}<strong>{{ target_name }}</strong>{% endblock %}
+      
+{% block content %}
+          <form class="form-vertical" action="{{ admin_index|url() }}" method="post">
+          	<div class="form-container">
+          	  <p class="lead">{{ task_name }}...</p>
+              <div class="progress progress-striped active">
+                <div class="bar" style="width: {{ progress }}%;"></div>
+              </div>
+          	  <p>{{ message }}</p>
+            </div>
+            <div class="form-actions">
+              <a href="{{ cancel_url }}" class="btn pull-right"><i class="icon-home"></i> {% trans %}Cancel Task{% endtrans %}</a>
+            </div>
+          </form>
+{% endblock %}

+ 11 - 0
templates/admin/users/admin_users_newsletters/list.html

@@ -0,0 +1,11 @@
+{% extends "admin/admin/list.html" %}
+{% load i18n %}
+{% load l10n %}
+{% load url from future %}
+{% import "_forms.html" as form_theme with context %}
+
+{% block table_row scoped %}
+  <td class="lead-cell">
+  	<strong>{{ item.name }}</strong>{% if item.ignore_subscriptions %} <span class="label label-important">{% trans %}Ignoring Subscriptions{% endtrans %}</span>{% endif %}
+  </td>
+{% endblock%}