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