]> git.ozlabs.org Git - patchwork/blob - apps/patchwork/models.py
Use hex strings for hash values
[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             self.hashlib = True
180             n_bytes = len(hashlib.new(self.algorithm).hexdigest())
181         except ImportError:
182             self.hashlib = False
183             if algorithm == 'sha1':
184                 import sha
185                 self.hash_constructor = sha.new
186             elif algorithm == 'md5':
187                 import md5
188                 self.hash_constructor = md5.new
189             else:
190                 raise NameError("Unknown algorithm '%s'" % algorithm)
191             n_bytes = len(self.hash_constructor().hexdigest())
192
193         kwargs['max_length'] = n_bytes
194         super(HashField, self).__init__(*args, **kwargs)
195
196     def db_type(self):
197         if self.hashlib:
198             import hashlib
199             n_bytes = len(hashlib.new(self.algorithm).hexdigest())
200         else:
201             n_bytes = len(self.hash_constructor().hexdigest())
202         return 'char(%d)' % n_bytes
203
204 class Patch(models.Model):
205     project = models.ForeignKey(Project)
206     msgid = models.CharField(max_length=255, unique = True)
207     name = models.CharField(max_length=255)
208     date = models.DateTimeField(default=datetime.datetime.now)
209     submitter = models.ForeignKey(Person)
210     delegate = models.ForeignKey(User, blank = True, null = True)
211     state = models.ForeignKey(State)
212     archived = models.BooleanField(default = False)
213     headers = models.TextField(blank = True)
214     content = models.TextField()
215     commit_ref = models.CharField(max_length=255, null = True, blank = True)
216     hash = HashField(null = True, db_index = True)
217
218     def __str__(self):
219         return self.name
220
221     def comments(self):
222         return Comment.objects.filter(patch = self)
223
224     def save(self):
225         try:
226             s = self.state
227         except:
228             self.state = State.objects.get(ordering =  0)
229
230         if self.hash is None:
231             self.hash = hash_patch(self.content).hexdigest()
232
233         super(Patch, self).save()
234
235     def is_editable(self, user):
236         if not user.is_authenticated():
237             return False
238
239         if self.submitter.user == user or self.delegate == user:
240             return True
241
242         profile = user.get_profile()
243         return self.project in user.get_profile().maintainer_projects.all()
244
245     def form(self):
246         f = PatchForm(instance = self, prefix = self.id)
247         return f
248
249     def filename(self):
250         fname_re = re.compile('[^-_A-Za-z0-9\.]+')
251         str = fname_re.sub('-', self.name)
252         return str.strip('-') + '.patch'
253
254     def mbox(self):
255         comment = None
256         try:
257             comment = Comment.objects.get(msgid = self.msgid)
258         except Exception:
259             pass
260
261         body = ''
262         if comment:
263             body = comment.content.strip() + "\n\n"
264         body += self.content
265
266         mail = MIMEText(body)
267         mail['Subject'] = self.name
268         mail['Date'] = email.utils.formatdate(
269                         time.mktime(self.date.utctimetuple()))
270         mail['From'] = str(self.submitter)
271         mail['X-Patchwork-Id'] = str(self.id)
272         mail.set_unixfrom('From patchwork ' + self.date.ctime())
273
274         return mail
275
276
277     @models.permalink
278     def get_absolute_url(self):
279         return ('patchwork.views.patch.patch', (), {'patch_id': self.id})
280
281     class Meta:
282         verbose_name_plural = 'Patches'
283         ordering = ['date']
284
285 class Comment(models.Model):
286     patch = models.ForeignKey(Patch)
287     msgid = models.CharField(max_length=255, unique = True)
288     submitter = models.ForeignKey(Person)
289     date = models.DateTimeField(default = datetime.datetime.now)
290     headers = models.TextField(blank = True)
291     content = models.TextField()
292
293     class Meta:
294         ordering = ['date']
295
296 class Bundle(models.Model):
297     owner = models.ForeignKey(User)
298     project = models.ForeignKey(Project)
299     name = models.CharField(max_length = 50, null = False, blank = False)
300     patches = models.ManyToManyField(Patch)
301     public = models.BooleanField(default = False)
302
303     def n_patches(self):
304         return self.patches.all().count()
305
306     class Meta:
307         unique_together = [('owner', 'name')]
308
309     def public_url(self):
310         if not self.public:
311             return None
312         site = Site.objects.get_current()
313         return 'http://%s%s' % (site.domain,
314                 reverse('patchwork.views.bundle.public',
315                         kwargs = {
316                                 'username': self.owner.username,
317                                 'bundlename': self.name
318                         }))
319
320     def mbox(self):
321         return '\n'.join([p.mbox().as_string(True) \
322                         for p in self.patches.all()])
323