]> git.ozlabs.org Git - patchwork/blob - apps/patchwork/models.py
[models] Improve comment filter
[patchwork] / apps / patchwork / models.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 from django.db import models
21 from django.contrib.auth.models import User
22 from django.core.urlresolvers import reverse
23 from django.contrib.sites.models import Site
24 from django.conf import settings
25 from patchwork.parser import hash_patch
26 import django.oldforms as oldforms
27
28 import re
29 import datetime, time
30 import string
31 import random
32
33 try:
34     from email.mime.text import MIMEText
35     import email.utils
36 except ImportError:
37     # Python 2.4 compatibility
38     from email.MIMEText import MIMEText
39     import email.Utils
40     email.utils = email.Utils
41
42 class Person(models.Model):
43     email = models.CharField(max_length=255, unique = True)
44     name = models.CharField(max_length=255, null = True)
45     user = models.ForeignKey(User, null = True)
46
47     def __str__(self):
48         if self.name:
49             return '%s <%s>' % (self.name, self.email)
50         else:
51             return self.email
52
53     def link_to_user(self, user):
54         self.name = user.get_profile().name()
55         self.user = user
56
57     class Meta:
58         verbose_name_plural = 'People'
59
60 class Project(models.Model):
61     linkname = models.CharField(max_length=255, unique=True)
62     name = models.CharField(max_length=255, unique=True)
63     listid = models.CharField(max_length=255, unique=True)
64     listemail = models.CharField(max_length=200)
65
66     def __str__(self):
67         return self.name
68
69 class UserProfile(models.Model):
70     user = models.ForeignKey(User, unique = True)
71     primary_project = models.ForeignKey(Project, null = True)
72     maintainer_projects = models.ManyToManyField(Project,
73             related_name = 'maintainer_project')
74     send_email = models.BooleanField(default = False,
75             help_text = 'Selecting this option allows patchwork to send ' +
76                 'email on your behalf')
77     patches_per_page = models.PositiveIntegerField(default = 100,
78             null = False, blank = False,
79             help_text = 'Number of patches to display per page')
80
81     def name(self):
82         if self.user.first_name or self.user.last_name:
83             names = filter(bool, [self.user.first_name, self.user.last_name])
84             return ' '.join(names)
85         return self.user.username
86
87     def contributor_projects(self):
88         submitters = Person.objects.filter(user = self.user)
89         return Project.objects \
90             .filter(id__in = \
91                     Patch.objects.filter(
92                         submitter__in = submitters) \
93                     .values('project_id').query)
94
95
96     def sync_person(self):
97         pass
98
99     def n_todo_patches(self):
100         return self.todo_patches().count()
101
102     def todo_patches(self, project = None):
103
104         # filter on project, if necessary
105         if project:
106             qs = Patch.objects.filter(project = project)
107         else:
108             qs = Patch.objects
109
110         qs = qs.filter(archived = False) \
111              .filter(delegate = self.user) \
112              .filter(state__in = \
113                      State.objects.filter(action_required = True) \
114                          .values('pk').query)
115         return qs
116
117     def save(self):
118         super(UserProfile, self).save()
119         people = Person.objects.filter(email = self.user.email)
120         if not people:
121             person = Person(email = self.user.email,
122                     name = self.name(), user = self.user)
123             person.save()
124         else:
125             for person in people:
126                  person.link_to_user(self.user)
127                  person.save()
128
129     def __str__(self):
130         return self.name()
131
132 class State(models.Model):
133     name = models.CharField(max_length = 100)
134     ordering = models.IntegerField(unique = True)
135     action_required = models.BooleanField(default = True)
136
137     def __str__(self):
138         return self.name
139
140     class Meta:
141         ordering = ['ordering']
142
143 class HashField(models.CharField):
144     __metaclass__ = models.SubfieldBase
145
146     def __init__(self, algorithm = 'sha1', *args, **kwargs):
147         self.algorithm = algorithm
148         try:
149             import hashlib
150             def _construct(string = ''):
151                 return hashlib.new(self.algorithm, string)
152             self.construct = _construct
153             self.n_bytes = len(hashlib.new(self.algorithm).hexdigest())
154         except ImportError:
155             modules = { 'sha1': 'sha', 'md5': 'md5'}
156
157             if algorithm not in modules.keys():
158                 raise NameError("Unknown algorithm '%s'" % algorithm)
159
160             self.construct = __import__(modules[algorithm]).new
161
162         self.n_bytes = len(self.construct().hexdigest())
163
164         kwargs['max_length'] = self.n_bytes
165         super(HashField, self).__init__(*args, **kwargs)
166
167     def db_type(self):
168         return 'char(%d)' % self.n_bytes
169
170 class Patch(models.Model):
171     project = models.ForeignKey(Project)
172     msgid = models.CharField(max_length=255, unique = True)
173     name = models.CharField(max_length=255)
174     date = models.DateTimeField(default=datetime.datetime.now)
175     submitter = models.ForeignKey(Person)
176     delegate = models.ForeignKey(User, blank = True, null = True)
177     state = models.ForeignKey(State)
178     archived = models.BooleanField(default = False)
179     headers = models.TextField(blank = True)
180     content = models.TextField()
181     commit_ref = models.CharField(max_length=255, null = True, blank = True)
182     hash = HashField(null = True, db_index = True)
183
184     def __str__(self):
185         return self.name
186
187     def comments(self):
188         return Comment.objects.filter(patch = self)
189
190     def save(self):
191         try:
192             s = self.state
193         except:
194             self.state = State.objects.get(ordering =  0)
195
196         if self.hash is None:
197             self.hash = hash_patch(self.content).hexdigest()
198
199         super(Patch, self).save()
200
201     def is_editable(self, user):
202         if not user.is_authenticated():
203             return False
204
205         if self.submitter.user == user or self.delegate == user:
206             return True
207
208         profile = user.get_profile()
209         return self.project in user.get_profile().maintainer_projects.all()
210
211     def form(self):
212         f = PatchForm(instance = self, prefix = self.id)
213         return f
214
215     def filename(self):
216         fname_re = re.compile('[^-_A-Za-z0-9\.]+')
217         str = fname_re.sub('-', self.name)
218         return str.strip('-') + '.patch'
219
220     def mbox(self):
221         comment = None
222         try:
223             comment = Comment.objects.get(patch = self, msgid = self.msgid)
224         except Exception:
225             pass
226
227         body = ''
228         if comment:
229             body = comment.content.strip() + "\n\n"
230         body += self.content
231
232         mail = MIMEText(body)
233         mail['Subject'] = self.name
234         mail['Date'] = email.utils.formatdate(
235                         time.mktime(self.date.utctimetuple()))
236         mail['From'] = str(self.submitter)
237         mail['X-Patchwork-Id'] = str(self.id)
238         mail.set_unixfrom('From patchwork ' + self.date.ctime())
239
240         return mail
241
242
243     @models.permalink
244     def get_absolute_url(self):
245         return ('patchwork.views.patch.patch', (), {'patch_id': self.id})
246
247     class Meta:
248         verbose_name_plural = 'Patches'
249         ordering = ['date']
250
251 class Comment(models.Model):
252     patch = models.ForeignKey(Patch)
253     msgid = models.CharField(max_length=255, unique = True)
254     submitter = models.ForeignKey(Person)
255     date = models.DateTimeField(default = datetime.datetime.now)
256     headers = models.TextField(blank = True)
257     content = models.TextField()
258
259     class Meta:
260         ordering = ['date']
261
262 class Bundle(models.Model):
263     owner = models.ForeignKey(User)
264     project = models.ForeignKey(Project)
265     name = models.CharField(max_length = 50, null = False, blank = False)
266     patches = models.ManyToManyField(Patch)
267     public = models.BooleanField(default = False)
268
269     def n_patches(self):
270         return self.patches.all().count()
271
272     class Meta:
273         unique_together = [('owner', 'name')]
274
275     def public_url(self):
276         if not self.public:
277             return None
278         site = Site.objects.get_current()
279         return 'http://%s%s' % (site.domain,
280                 reverse('patchwork.views.bundle.public',
281                         kwargs = {
282                                 'username': self.owner.username,
283                                 'bundlename': self.name
284                         }))
285
286     def mbox(self):
287         return '\n'.join([p.mbox().as_string(True) \
288                         for p in self.patches.all()])
289
290 class UserPersonConfirmation(models.Model):
291     user = models.ForeignKey(User)
292     email = models.CharField(max_length = 200)
293     key = HashField()
294     date = models.DateTimeField(default=datetime.datetime.now)
295     active = models.BooleanField(default = True)
296
297     def confirm(self):
298         if not self.active:
299             return
300         person = None
301         try:
302             person = Person.objects.get(email = self.email)
303         except Exception:
304             pass
305         if not person:
306             person = Person(email = self.email)
307
308         person.link_to_user(self.user)
309         person.save()
310         self.active = False
311         self.save()
312
313     def save(self):
314         max = 1 << 32
315         if self.key == '':
316             str = '%s%s%d' % (self.user, self.email, random.randint(0, max))
317             self.key = self._meta.get_field('key').construct(str).hexdigest()
318         super(UserPersonConfirmation, self).save()
319
320