]> git.ozlabs.org Git - patchwork/blob - apps/patchwork/models.py
Add HashField.construct() method
[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 def _confirm_key():
133     allowedchars = string.ascii_lowercase + string.digits
134     str = ''
135     for i in range(1, 32):
136         str += random.choice(allowedchars)
137     return str;
138
139 class UserPersonConfirmation(models.Model):
140     user = models.ForeignKey(User)
141     email = models.CharField(max_length = 200)
142     key = models.CharField(max_length = 32, default = _confirm_key)
143     date = models.DateTimeField(default=datetime.datetime.now)
144     active = models.BooleanField(default = True)
145
146     def confirm(self):
147         if not self.active:
148             return
149         person = None
150         try:
151             person = Person.objects.get(email = self.email)
152         except Exception:
153             pass
154         if not person:
155             person = Person(email = self.email)
156
157         person.link_to_user(self.user)
158         person.save()
159         self.active = False
160
161 class State(models.Model):
162     name = models.CharField(max_length = 100)
163     ordering = models.IntegerField(unique = True)
164     action_required = models.BooleanField(default = True)
165
166     def __str__(self):
167         return self.name
168
169     class Meta:
170         ordering = ['ordering']
171
172 class HashField(models.CharField):
173     __metaclass__ = models.SubfieldBase
174
175     def __init__(self, algorithm = 'sha1', *args, **kwargs):
176         self.algorithm = algorithm
177         try:
178             import hashlib
179             def _construct(string = ''):
180                 return hashlib.new(self.algorithm, string)
181             self.construct = _construct
182             self.n_bytes = len(hashlib.new(self.algorithm).hexdigest())
183         except ImportError:
184             modules = { 'sha1': 'sha', 'md5': 'md5'}
185
186             if algorithm not in modules.keys():
187                 raise NameError("Unknown algorithm '%s'" % algorithm)
188
189             self.construct = __import__(modules[algorithm]).new
190
191         self.n_bytes = len(self.construct().hexdigest())
192
193         kwargs['max_length'] = self.n_bytes
194         super(HashField, self).__init__(*args, **kwargs)
195
196     def db_type(self):
197         return 'char(%d)' % self.n_bytes
198
199 class Patch(models.Model):
200     project = models.ForeignKey(Project)
201     msgid = models.CharField(max_length=255, unique = True)
202     name = models.CharField(max_length=255)
203     date = models.DateTimeField(default=datetime.datetime.now)
204     submitter = models.ForeignKey(Person)
205     delegate = models.ForeignKey(User, blank = True, null = True)
206     state = models.ForeignKey(State)
207     archived = models.BooleanField(default = False)
208     headers = models.TextField(blank = True)
209     content = models.TextField()
210     commit_ref = models.CharField(max_length=255, null = True, blank = True)
211     hash = HashField(null = True, db_index = True)
212
213     def __str__(self):
214         return self.name
215
216     def comments(self):
217         return Comment.objects.filter(patch = self)
218
219     def save(self):
220         try:
221             s = self.state
222         except:
223             self.state = State.objects.get(ordering =  0)
224
225         if self.hash is None:
226             self.hash = hash_patch(self.content).hexdigest()
227
228         super(Patch, self).save()
229
230     def is_editable(self, user):
231         if not user.is_authenticated():
232             return False
233
234         if self.submitter.user == user or self.delegate == user:
235             return True
236
237         profile = user.get_profile()
238         return self.project in user.get_profile().maintainer_projects.all()
239
240     def form(self):
241         f = PatchForm(instance = self, prefix = self.id)
242         return f
243
244     def filename(self):
245         fname_re = re.compile('[^-_A-Za-z0-9\.]+')
246         str = fname_re.sub('-', self.name)
247         return str.strip('-') + '.patch'
248
249     def mbox(self):
250         comment = None
251         try:
252             comment = Comment.objects.get(msgid = self.msgid)
253         except Exception:
254             pass
255
256         body = ''
257         if comment:
258             body = comment.content.strip() + "\n\n"
259         body += self.content
260
261         mail = MIMEText(body)
262         mail['Subject'] = self.name
263         mail['Date'] = email.utils.formatdate(
264                         time.mktime(self.date.utctimetuple()))
265         mail['From'] = str(self.submitter)
266         mail['X-Patchwork-Id'] = str(self.id)
267         mail.set_unixfrom('From patchwork ' + self.date.ctime())
268
269         return mail
270
271
272     @models.permalink
273     def get_absolute_url(self):
274         return ('patchwork.views.patch.patch', (), {'patch_id': self.id})
275
276     class Meta:
277         verbose_name_plural = 'Patches'
278         ordering = ['date']
279
280 class Comment(models.Model):
281     patch = models.ForeignKey(Patch)
282     msgid = models.CharField(max_length=255, unique = True)
283     submitter = models.ForeignKey(Person)
284     date = models.DateTimeField(default = datetime.datetime.now)
285     headers = models.TextField(blank = True)
286     content = models.TextField()
287
288     class Meta:
289         ordering = ['date']
290
291 class Bundle(models.Model):
292     owner = models.ForeignKey(User)
293     project = models.ForeignKey(Project)
294     name = models.CharField(max_length = 50, null = False, blank = False)
295     patches = models.ManyToManyField(Patch)
296     public = models.BooleanField(default = False)
297
298     def n_patches(self):
299         return self.patches.all().count()
300
301     class Meta:
302         unique_together = [('owner', 'name')]
303
304     def public_url(self):
305         if not self.public:
306             return None
307         site = Site.objects.get_current()
308         return 'http://%s%s' % (site.domain,
309                 reverse('patchwork.views.bundle.public',
310                         kwargs = {
311                                 'username': self.owner.username,
312                                 'bundlename': self.name
313                         }))
314
315     def mbox(self):
316         return '\n'.join([p.mbox().as_string(True) \
317                         for p in self.patches.all()])
318