1 # Patchwork - automated patch tracking system
2 # Copyright (C) 2008 Jeremy Kerr <jk@ozlabs.org>
4 # This file is part of the Patchwork package.
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.
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.
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
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
34 from email.mime.text import MIMEText
37 # Python 2.4 compatibility
38 from email.MIMEText import MIMEText
40 email.utils = email.Utils
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)
49 return '%s <%s>' % (self.name, self.email)
53 def link_to_user(self, user):
54 self.name = user.get_profile().name()
58 verbose_name_plural = 'People'
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)
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')
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
87 def contributor_projects(self):
88 submitters = Person.objects.filter(user = self.user)
89 return Project.objects \
92 submitter__in = submitters) \
93 .values('project_id').query)
96 def sync_person(self):
99 def n_todo_patches(self):
100 return self.todo_patches().count()
102 def todo_patches(self, project = None):
104 # filter on project, if necessary
106 qs = Patch.objects.filter(project = project)
110 qs = qs.filter(archived = False) \
111 .filter(delegate = self.user) \
112 .filter(state__in = \
113 State.objects.filter(action_required = True) \
118 super(UserProfile, self).save()
119 people = Person.objects.filter(email = self.user.email)
121 person = Person(email = self.user.email,
122 name = self.name(), user = self.user)
125 for person in people:
126 person.link_to_user(self.user)
133 allowedchars = string.ascii_lowercase + string.digits
135 for i in range(1, 32):
136 str += random.choice(allowedchars)
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)
151 person = Person.objects.get(email = self.email)
155 person = Person(email = self.email)
157 person.link_to_user(self.user)
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)
170 ordering = ['ordering']
172 class HashField(models.CharField):
173 __metaclass__ = models.SubfieldBase
175 def __init__(self, algorithm = 'sha1', *args, **kwargs):
176 self.algorithm = algorithm
180 n_bytes = len(hashlib.new(self.algorithm).hexdigest())
183 if algorithm == 'sha1':
185 self.hash_constructor = sha.new
186 elif algorithm == 'md5':
188 self.hash_constructor = md5.new
190 raise NameError("Unknown algorithm '%s'" % algorithm)
191 n_bytes = len(self.hash_constructor().hexdigest())
193 kwargs['max_length'] = n_bytes
194 super(HashField, self).__init__(*args, **kwargs)
199 n_bytes = len(hashlib.new(self.algorithm).hexdigest())
201 n_bytes = len(self.hash_constructor().hexdigest())
202 return 'char(%d)' % n_bytes
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)
222 return Comment.objects.filter(patch = self)
228 self.state = State.objects.get(ordering = 0)
230 if self.hash is None:
231 self.hash = hash_patch(self.content).hexdigest()
233 super(Patch, self).save()
235 def is_editable(self, user):
236 if not user.is_authenticated():
239 if self.submitter.user == user or self.delegate == user:
242 profile = user.get_profile()
243 return self.project in user.get_profile().maintainer_projects.all()
246 f = PatchForm(instance = self, prefix = self.id)
250 fname_re = re.compile('[^-_A-Za-z0-9\.]+')
251 str = fname_re.sub('-', self.name)
252 return str.strip('-') + '.patch'
257 comment = Comment.objects.get(msgid = self.msgid)
263 body = comment.content.strip() + "\n\n"
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())
278 def get_absolute_url(self):
279 return ('patchwork.views.patch.patch', (), {'patch_id': self.id})
282 verbose_name_plural = 'Patches'
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()
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)
304 return self.patches.all().count()
307 unique_together = [('owner', 'name')]
309 def public_url(self):
312 site = Site.objects.get_current()
313 return 'http://%s%s' % (site.domain,
314 reverse('patchwork.views.bundle.public',
316 'username': self.owner.username,
317 'bundlename': self.name
321 return '\n'.join([p.mbox().as_string(True) \
322 for p in self.patches.all()])