]> git.ozlabs.org Git - patchwork/blob - apps/patchwork/models.py
In some places tabs are used instead of spaces for indentation,
[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 == 'postgresql':
198             return 'bytea'
199         elif settings.DATABASE_ENGINE == 'mysql':
200             return 'binary(%d)' % n_bytes
201
202     def to_python(self, value):
203         return value
204
205     def get_db_prep_save(self, value):
206         return ''.join(map(lambda x: '\\%03o' % ord(x), value))
207
208     def get_manipulator_field_objs(self):
209         return [oldforms.TextField]
210
211 class Patch(models.Model):
212     project = models.ForeignKey(Project)
213     msgid = models.CharField(max_length=255, unique = True)
214     name = models.CharField(max_length=255)
215     date = models.DateTimeField(default=datetime.datetime.now)
216     submitter = models.ForeignKey(Person)
217     delegate = models.ForeignKey(User, blank = True, null = True)
218     state = models.ForeignKey(State)
219     archived = models.BooleanField(default = False)
220     headers = models.TextField(blank = True)
221     content = models.TextField()
222     commit_ref = models.CharField(max_length=255, null = True, blank = True)
223     hash = HashField()
224
225     def __str__(self):
226         return self.name
227
228     def comments(self):
229         return Comment.objects.filter(patch = self)
230
231     def save(self):
232         try:
233             s = self.state
234         except:
235             self.state = State.objects.get(ordering =  0)
236         if hash is None:
237             print "no hash"
238         super(Patch, self).save()
239
240     def is_editable(self, user):
241         if not user.is_authenticated():
242             return False
243
244         if self.submitter.user == user or self.delegate == user:
245             return True
246
247         profile = user.get_profile()
248         return self.project in user.get_profile().maintainer_projects.all()
249
250     def form(self):
251         f = PatchForm(instance = self, prefix = self.id)
252         return f
253
254     def filename(self):
255         fname_re = re.compile('[^-_A-Za-z0-9\.]+')
256         str = fname_re.sub('-', self.name)
257         return str.strip('-') + '.patch'
258
259     def mbox(self):
260         comment = None
261         try:
262             comment = Comment.objects.get(msgid = self.msgid)
263         except Exception:
264             pass
265
266         body = ''
267         if comment:
268             body = comment.content.strip() + "\n\n"
269         body += self.content
270
271         mail = MIMEText(body)
272         mail['Subject'] = self.name
273         mail['Date'] = email.utils.formatdate(
274                         time.mktime(self.date.utctimetuple()))
275         mail['From'] = str(self.submitter)
276         mail['X-Patchwork-Id'] = str(self.id)
277         mail.set_unixfrom('From patchwork ' + self.date.ctime())
278
279         return mail
280
281
282     @models.permalink
283     def get_absolute_url(self):
284         return ('patchwork.views.patch.patch', (), {'patch_id': self.id})
285
286     class Meta:
287         verbose_name_plural = 'Patches'
288         ordering = ['date']
289
290 class Comment(models.Model):
291     patch = models.ForeignKey(Patch)
292     msgid = models.CharField(max_length=255, unique = True)
293     submitter = models.ForeignKey(Person)
294     date = models.DateTimeField(default = datetime.datetime.now)
295     headers = models.TextField(blank = True)
296     content = models.TextField()
297
298     class Meta:
299         ordering = ['date']
300
301 class Bundle(models.Model):
302     owner = models.ForeignKey(User)
303     project = models.ForeignKey(Project)
304     name = models.CharField(max_length = 50, null = False, blank = False)
305     patches = models.ManyToManyField(Patch)
306     public = models.BooleanField(default = False)
307
308     def n_patches(self):
309         return self.patches.all().count()
310
311     class Meta:
312         unique_together = [('owner', 'name')]
313
314     def public_url(self):
315         if not self.public:
316             return None
317         site = Site.objects.get_current()
318         return 'http://%s%s' % (site.domain,
319                 reverse('patchwork.views.bundle.public',
320                         kwargs = {
321                                 'username': self.owner.username,
322                                 'bundlename': self.name
323                         }))
324
325     def mbox(self):
326         return '\n'.join([p.mbox().as_string(True) \
327                         for p in self.patches.all()])
328