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 self.n_bytes = len(hashlib.new(self.algorithm).hexdigest())
183 if algorithm == 'sha1':
185 hash_constructor = sha.new
186 elif algorithm == 'md5':
188 hash_constructor = md5.new
190 raise NameError("Unknown algorithm '%s'" % algorithm)
191 self.n_bytes = len(hash_constructor().hexdigest())
193 kwargs['max_length'] = self.n_bytes
194 super(HashField, self).__init__(*args, **kwargs)
197 return 'char(%d)' % self.n_bytes
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)
217 return Comment.objects.filter(patch = self)
223 self.state = State.objects.get(ordering = 0)
225 if self.hash is None:
226 self.hash = hash_patch(self.content).hexdigest()
228 super(Patch, self).save()
230 def is_editable(self, user):
231 if not user.is_authenticated():
234 if self.submitter.user == user or self.delegate == user:
237 profile = user.get_profile()
238 return self.project in user.get_profile().maintainer_projects.all()
241 f = PatchForm(instance = self, prefix = self.id)
245 fname_re = re.compile('[^-_A-Za-z0-9\.]+')
246 str = fname_re.sub('-', self.name)
247 return str.strip('-') + '.patch'
252 comment = Comment.objects.get(msgid = self.msgid)
258 body = comment.content.strip() + "\n\n"
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())
273 def get_absolute_url(self):
274 return ('patchwork.views.patch.patch', (), {'patch_id': self.id})
277 verbose_name_plural = 'Patches'
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()
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)
299 return self.patches.all().count()
302 unique_together = [('owner', 'name')]
304 def public_url(self):
307 site = Site.objects.get_current()
308 return 'http://%s%s' % (site.domain,
309 reverse('patchwork.views.bundle.public',
311 'username': self.owner.username,
312 'bundlename': self.name
316 return '\n'.join([p.mbox().as_string(True) \
317 for p in self.patches.all()])