0001-import-subscribers-from-csv-14010.patch
corbo/forms.py | ||
---|---|---|
1 |
import csv |
|
2 | ||
1 | 3 |
from django import forms |
2 | 4 |
from django.utils.translation import ugettext_lazy as _ |
3 | 5 |
from django.utils.text import slugify |
4 | 6 |
from django.core.exceptions import ObjectDoesNotExist |
7 |
from django.core import validators |
|
8 |
from django.core.exceptions import ValidationError |
|
5 | 9 | |
6 | 10 |
from .models import Announce, Category, Broadcast, channel_choices |
7 | 11 | |
... | ... | |
45 | 49 |
slug = '%s-%s' % (base_slug, i) |
46 | 50 |
self.instance.slug = slug |
47 | 51 |
return super(CategoryForm, self).save(commit=commit) |
52 | ||
53 | ||
54 |
class SubscriptionsImportForm(forms.Form): |
|
55 |
subscribers = forms.FileField(_('Subscribers'), |
|
56 |
help_text=_('CSV file with email addresses on first column')) |
|
57 | ||
58 |
def clean_subscribers(self, *args, **kwargs): |
|
59 |
subscribers = [] |
|
60 |
reader = csv.reader(self.cleaned_data['subscribers']) |
|
61 |
for idx, row in enumerate(reader, 1): |
|
62 |
if not row[0]: |
|
63 |
continue |
|
64 |
try: |
|
65 |
validators.validate_email(row[0]) |
|
66 |
subscribers.append(row[0]) |
|
67 |
except ValidationError: |
|
68 |
raise ValidationError(_('Invalid email address at line %s' % idx)) |
|
69 |
return subscribers |
corbo/manage_urls.py | ||
---|---|---|
2 | 2 | |
3 | 3 |
from .views import add_announce, edit_announce, delete_announce, \ |
4 | 4 |
add_category, edit_category, view_category, delete_category, manage, \ |
5 |
menu_json |
|
5 |
subscriptions_import, menu_json
|
|
6 | 6 | |
7 | 7 |
urlpatterns = patterns('', |
8 | 8 |
url(r'^$', manage, name='manage'), |
... | ... | |
20 | 20 |
name='edit_category'), |
21 | 21 |
url(r'^category/delete/(?P<slug>[\w-]+)$', delete_category, |
22 | 22 |
name='delete_category'), |
23 |
url(r'^category/(?P<slug>[\w-]+)/import-subscriptions$', subscriptions_import, |
|
24 |
name='subscriptions-import'), |
|
23 | 25 |
url(r'^menu.json$', menu_json), |
24 | 26 |
) |
corbo/static/css/corbo.css | ||
---|---|---|
165 | 165 |
border-radius: 0.3em; |
166 | 166 |
text-transform: uppercase; |
167 | 167 |
} |
168 | ||
169 |
#appbar .menu-opener { |
|
170 |
border: none; |
|
171 |
cursor: pointer; |
|
172 |
} |
|
173 | ||
174 |
#appbar .menu { |
|
175 |
background: white; |
|
176 |
padding: 10px; |
|
177 |
list-style: none; |
|
178 |
position: absolute; |
|
179 |
right: 0px; |
|
180 |
margin-top: -24px; |
|
181 |
border: 1px solid #d0d0d0; |
|
182 |
background: #fafafa; |
|
183 |
box-shadow: 0px 1px 1px 2px rgba(0, 0, 0, 0.04); |
|
184 |
z-index: 100; |
|
185 |
} |
|
186 | ||
187 |
#appbar .menu a { |
|
188 |
float: none; |
|
189 |
top: 0; |
|
190 |
right: 0; |
|
191 |
margin: 0; |
|
192 |
display: block; |
|
193 |
min-width: 12em; |
|
194 |
} |
|
195 | ||
196 |
#appbar .menu li { |
|
197 |
margin: 1ex 0; |
|
198 |
} |
|
199 | ||
200 |
#appbar .menu li a { |
|
201 |
border: none; |
|
202 |
} |
|
203 | ||
204 |
#appbar .menu li a:hover { |
|
205 |
background: white; |
|
206 |
} |
corbo/static/js/corbo.manager.js | ||
---|---|---|
1 |
$(function() { |
|
2 |
$('a.menu-opener').on('click', function() { |
|
3 |
$('#appbar .menu').toggle(); |
|
4 |
}); |
|
5 |
}); |
|
6 |
corbo/templates/corbo/base.html | ||
---|---|---|
19 | 19 |
<link rel="stylesheet" type="text/css" media="all" href="{% static 'css/datetimepicker.css' %}" /> |
20 | 20 |
<script src="{% static 'js/bootstrap-datetimepicker.js' %}"></script> |
21 | 21 |
<script src="{% static 'js/locales/bootstrap-datetimepicker.fr.js' %}"></script> |
22 |
<script src="{% static 'js/corbo.manager.js' %}"></script> |
|
23 |
<script src="{% static 'js/gadjo.js' %}"></script> |
|
22 | 24 |
{% endblock %} |
23 | 25 | |
24 | 26 |
{% block logout-url %} |
corbo/templates/corbo/category_detail.html | ||
---|---|---|
10 | 10 | |
11 | 11 |
{% block appbar %} |
12 | 12 |
<h2>{{ object.name }}</h2> |
13 |
<a class="menu-opener">☰</a> |
|
14 |
<ul class="menu" style="display: none"> |
|
15 |
<li><a href="{% url 'subscriptions-import' slug=category.slug %}" rel="popup">{% trans 'Import subscriptions' %}</a></li> |
|
16 |
</ul> |
|
17 | ||
13 | 18 |
<a href="{% url 'delete_category' slug=object.slug %}" rel="popup">{% trans 'Delete' %}</a> |
14 | 19 |
<a href="{% url 'edit_category' slug=object.slug %}" rel="popup">{% trans 'Edit' %}</a> |
15 | 20 |
<a href="{% url 'add_announce' slug=object.slug %}">{% trans 'New announce' %}</a> |
corbo/templates/corbo/subscriptions_import_form.html | ||
---|---|---|
1 |
{% extends "corbo/category_detail.html" %} |
|
2 |
{% load i18n static %} |
|
3 | ||
4 |
{% block breadcrumb %} |
|
5 |
{{ block.super }} |
|
6 |
<a href='{% url "view_category" slug=category.slug %}'>{{ category }}</a> |
|
7 |
{% endblock %} |
|
8 | ||
9 |
{% block appbar %} |
|
10 |
<h2>{% trans "Import subscriptions" %}</h2> |
|
11 |
{% endblock %} |
|
12 | ||
13 | ||
14 |
{% block content %} |
|
15 |
<form method="post" enctype="multipart/form-data"> |
|
16 |
{% csrf_token %} |
|
17 |
{{ form }} |
|
18 |
<div class="buttons"> |
|
19 |
<button>{% trans "Save" %}</button> |
|
20 |
<a href="{% url "view_category" slug=category.slug %}" class="cancel">{% trans "Cancel" %}</a> |
|
21 |
</div> |
|
22 |
</form> |
|
23 |
{% endblock %} |
corbo/views.py | ||
---|---|---|
6 | 6 |
from django.utils import timezone |
7 | 7 |
from django.core.urlresolvers import reverse |
8 | 8 |
from django.views.generic import CreateView, UpdateView, DeleteView, \ |
9 |
ListView, TemplateView, RedirectView, DetailView |
|
9 |
ListView, TemplateView, RedirectView, DetailView, FormView
|
|
10 | 10 |
from django.contrib.syndication.views import Feed |
11 | 11 |
from django.shortcuts import resolve_url |
12 | 12 |
from django.utils.encoding import force_text |
... | ... | |
14 | 14 |
from django.http import HttpResponseRedirect, HttpResponse, Http404 |
15 | 15 |
from django.contrib.auth import logout as auth_logout |
16 | 16 |
from django.contrib.auth import views as auth_views |
17 |
from django.contrib import messages |
|
17 | 18 |
from django.utils.translation import ugettext_lazy as _ |
18 | 19 | |
19 | 20 |
import models |
20 |
from .forms import AnnounceForm, CategoryForm |
|
21 |
from .forms import AnnounceForm, CategoryForm, SubscriptionsImportForm
|
|
21 | 22 | |
22 | 23 |
try: |
23 | 24 |
from mellon.utils import get_idps |
... | ... | |
222 | 223 |
atom = AtomView() |
223 | 224 | |
224 | 225 | |
226 |
class SubscriptionsImportView(FormView): |
|
227 |
form_class = SubscriptionsImportForm |
|
228 |
template_name = 'corbo/subscriptions_import_form.html' |
|
229 | ||
230 |
def get_context_data(self, **kwargs): |
|
231 |
context = super(SubscriptionsImportView, self).get_context_data(**kwargs) |
|
232 |
context['category'] = models.Category.objects.get(slug=self.kwargs['slug']) |
|
233 |
return context |
|
234 | ||
235 |
def get_success_url(self): |
|
236 |
category = models.Category.objects.get(slug=self.kwargs['slug']) |
|
237 |
return reverse('view_category', kwargs={'slug': category.slug}) |
|
238 | ||
239 |
def form_valid(self, form): |
|
240 |
new = 0 |
|
241 |
c = models.Category.objects.get(slug=self.kwargs['slug']) |
|
242 |
for email in form.cleaned_data['subscribers']: |
|
243 |
obj, created = models.Subscription.objects.get_or_create(category=c, identifier='mailto:%s' % email) |
|
244 |
if created: |
|
245 |
new += 1 |
|
246 |
messages.info(self.request, _('%s subscriptions added') % new) |
|
247 |
return super(SubscriptionsImportView, self).form_valid(form) |
|
248 | ||
249 |
subscriptions_import = SubscriptionsImportView.as_view() |
|
250 | ||
251 | ||
225 | 252 |
def menu_json(request): |
226 | 253 |
label = _('Announces') |
227 | 254 |
json_str = json.dumps([{'label': force_text(label), |
228 |
- |