]> git.ozlabs.org Git - patchwork/blob - patchwork/views/__init__.py
trivial: Remove Python < 2.5 code
[patchwork] / patchwork / views / __init__.py
1 # Patchwork - automated patch tracking system
2 # Copyright (C) 2008 Jeremy Kerr <jk@ozlabs.org>
3 #
4 # This file is part of the Patchwork package.
5 #
6 # Patchwork is free software; you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation; either version 2 of the License, or
9 # (at your option) any later version.
10 #
11 # Patchwork is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 # GNU General Public License for more details.
15 #
16 # You should have received a copy of the GNU General Public License
17 # along with Patchwork; if not, write to the Free Software
18 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
19
20
21 from base import *
22 from patchwork.utils import Order, get_patch_ids, bundle_actions, set_bundle
23 from patchwork.paginator import Paginator
24 from patchwork.forms import MultiplePatchForm
25 from patchwork.models import Comment
26 import re
27 import datetime
28
29 from email.mime.nonmultipart import MIMENonMultipart
30 from email.encoders import encode_7or8bit
31 from email.parser import HeaderParser
32 from email.header import Header
33 import email.utils
34
35 def generic_list(request, project, view,
36         view_args = {}, filter_settings = [], patches = None,
37         editable_order = False):
38
39     context = PatchworkRequestContext(request,
40             list_view = view,
41             list_view_params = view_args)
42
43     context.project = project
44     data = {}
45     if request.method == 'GET':
46         data = request.GET
47     elif request.method == 'POST':
48         data = request.POST
49     order = Order(data.get('order'), editable=editable_order)
50
51     # Explicitly set data to None because request.POST will be an empty dict
52     # when the form is not submitted, but passing a non-None data argument to
53     # a forms.Form will make it bound and we don't want that to happen unless
54     # there's been a form submission.
55     if request.method != 'POST':
56         data = None
57     user = request.user
58     properties_form = None
59     if project.is_editable(user):
60
61         # we only pass the post data to the MultiplePatchForm if that was
62         # the actual form submitted
63         data_tmp = None
64         if data and data.get('form', '') == 'patchlistform':
65             data_tmp = data
66
67         properties_form = MultiplePatchForm(project, data = data_tmp)
68
69     if request.method == 'POST' and data.get('form') == 'patchlistform':
70         action = data.get('action', '').lower()
71
72         # special case: the user may have hit enter in the 'create bundle'
73         # text field, so if non-empty, assume the create action:
74         if data.get('bundle_name', False):
75             action = 'create'
76
77         ps = Patch.objects.filter(id__in = get_patch_ids(data))
78
79         if action in bundle_actions:
80             errors = set_bundle(user, project, action, data, ps, context)
81
82         elif properties_form and action == properties_form.action:
83             errors = process_multiplepatch_form(properties_form, user,
84                                                 action, ps, context)
85         else:
86             errors = []
87
88         if errors:
89             context['errors'] = errors
90
91     for (filterclass, setting) in filter_settings:
92         if isinstance(setting, dict):
93             context.filters.set_status(filterclass, **setting)
94         elif isinstance(setting, list):
95             context.filters.set_status(filterclass, *setting)
96         else:
97             context.filters.set_status(filterclass, setting)
98
99     if patches is None:
100         patches = Patch.objects.filter(project=project)
101
102     # annotate with tag counts
103     patches = patches.with_tag_counts(project)
104
105     patches = context.filters.apply(patches)
106     if not editable_order:
107         patches = order.apply(patches)
108
109     # we don't need the content or headers for a list; they're text fields
110     # that can potentially contain a lot of data
111     patches = patches.defer('content', 'headers')
112
113     # but we will need to follow the state and submitter relations for
114     # rendering the list template
115     patches = patches.select_related('state', 'submitter', 'delegate')
116
117     paginator = Paginator(request, patches)
118
119     context.update({
120             'page':             paginator.current_page,
121             'patchform':        properties_form,
122             'project':          project,
123             'order':            order,
124             })
125
126     return context
127
128
129 def process_multiplepatch_form(form, user, action, patches, context):
130     errors = []
131     if not form.is_valid() or action != form.action:
132         return ['The submitted form data was invalid']
133
134     if len(patches) == 0:
135         context.add_message("No patches selected; nothing updated")
136         return errors
137
138     changed_patches = 0
139     for patch in patches:
140         if not patch.is_editable(user):
141             errors.append("You don't have permissions to edit patch '%s'"
142                             % patch.name)
143             continue
144
145         changed_patches += 1
146         form.save(patch)
147
148     if changed_patches == 1:
149         context.add_message("1 patch updated")
150     elif changed_patches > 1:
151         context.add_message("%d patches updated" % changed_patches)
152     else:
153         context.add_message("No patches updated")
154
155     return errors
156
157 class PatchMbox(MIMENonMultipart):
158     patch_charset = 'utf-8'
159     def __init__(self, _text):
160         MIMENonMultipart.__init__(self, 'text', 'plain',
161                         **{'charset': self.patch_charset})
162         self.set_payload(_text.encode(self.patch_charset))
163         encode_7or8bit(self)
164
165 def patch_to_mbox(patch):
166     postscript_re = re.compile('\n-{2,3} ?\n')
167
168     comment = None
169     try:
170         comment = Comment.objects.get(patch = patch, msgid = patch.msgid)
171     except Exception:
172         pass
173
174     body = ''
175     if comment:
176         body = comment.content.strip() + "\n"
177
178     parts = postscript_re.split(body, 1)
179     if len(parts) == 2:
180         (body, postscript) = parts
181         body = body.strip() + "\n"
182         postscript = postscript.rstrip()
183     else:
184         postscript = ''
185
186     for comment in Comment.objects.filter(patch = patch) \
187             .exclude(msgid = patch.msgid):
188         body += comment.patch_responses()
189
190     if postscript:
191         body += '---\n' + postscript + '\n'
192
193     if patch.content:
194         body += '\n' + patch.content
195
196     delta = patch.date - datetime.datetime.utcfromtimestamp(0)
197     utc_timestamp = delta.seconds + delta.days*24*3600
198
199     mail = PatchMbox(body)
200     mail['Subject'] = patch.name
201     mail['From'] = email.utils.formataddr((
202                     str(Header(patch.submitter.name, mail.patch_charset)),
203                     patch.submitter.email))
204     mail['X-Patchwork-Id'] = str(patch.id)
205     mail['Message-Id'] = patch.msgid
206     mail.set_unixfrom('From patchwork ' + patch.date.ctime())
207
208
209     copied_headers = ['To', 'Cc', 'Date']
210     orig_headers = HeaderParser().parsestr(str(patch.headers))
211     for header in copied_headers:
212         if header in orig_headers:
213             mail[header] = orig_headers[header]
214
215     if 'Date' not in mail:
216         mail['Date'] = email.utils.formatdate(utc_timestamp)
217
218     return mail