@@ -589,6 +589,12 @@ class Series(models.Model):
return self.total <= self.received_total
+ def filename(self):
+ fname_re = re.compile(r'[^-_A-Za-z0-9\.]+')
+ fname = fname_re.sub('-', str(self))
+ return fname.strip('-') + '.patch'
def add_cover_letter(self, cover):
"""Add a cover letter to the series.
@@ -32,6 +32,7 @@ from patchwork.views import notification as notification_views
from patchwork.views import patch as patch_views
from patchwork.views import project as project_views
from patchwork.views import pwclient as pwclient_views
+from patchwork.views import series as series_views
from patchwork.views import user as user_views
from patchwork.views import xmlrpc as xmlrpc_views
@@ -61,10 +62,14 @@ urlpatterns = [
- # comment urls
+ # comment views
+ # series views
+ url(r'^series/(?P<series_id>\d+)/mbox/$', series_views.series_mbox,
# logged-in user stuff
url(r'^user/$', user_views.profile, name='user-profile'),
new file mode 100644
@@ -0,0 +1,35 @@
+# Patchwork - automated patch tracking system
+# Copyright (C) 2017 Stephen Finucane <email@example.com>
+# This file is part of the Patchwork package.
+# Patchwork is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+# Patchwork is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+# You should have received a copy of the GNU General Public License
+# along with Patchwork; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+from django.http import HttpResponse
+from django.shortcuts import get_object_or_404
+from patchwork.models import Series
+from patchwork.views.utils import series_to_mbox
+def series_mbox(request, series_id):
+ series = get_object_or_404(Series, id=series_id)
+ response = HttpResponse(content_type='text/plain')
+ response['Content-Disposition'] = 'attachment; filename=' + \
+ series.filename.replace(';', '').replace('\n', '')
+ return response
@@ -151,3 +151,20 @@ def series_patch_to_mbox(patch, series_num):
+ """Get an mbox representation of an entire series.
+ series: The Series object to convert.
+ A string for the mbox file.
+ mbox = 
+ for dep in series.seriespatch_set.all():
+ return '\n'.join(mbox)