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