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)
132 class State(models.Model):
133 name = models.CharField(max_length = 100)
134 ordering = models.IntegerField(unique = True)
135 action_required = models.BooleanField(default = True)
141 ordering = ['ordering']
143 class HashField(models.CharField):
144 __metaclass__ = models.SubfieldBase
146 def __init__(self, algorithm = 'sha1', *args, **kwargs):
147 self.algorithm = algorithm
150 def _construct(string = ''):
151 return hashlib.new(self.algorithm, string)
152 self.construct = _construct
153 self.n_bytes = len(hashlib.new(self.algorithm).hexdigest())
155 modules = { 'sha1': 'sha', 'md5': 'md5'}
157 if algorithm not in modules.keys():
158 raise NameError("Unknown algorithm '%s'" % algorithm)
160 self.construct = __import__(modules[algorithm]).new
162 self.n_bytes = len(self.construct().hexdigest())
164 kwargs['max_length'] = self.n_bytes
165 super(HashField, self).__init__(*args, **kwargs)
168 return 'char(%d)' % self.n_bytes
170 class Patch(models.Model):
171 project = models.ForeignKey(Project)
172 msgid = models.CharField(max_length=255, unique = True)
173 name = models.CharField(max_length=255)
174 date = models.DateTimeField(default=datetime.datetime.now)
175 submitter = models.ForeignKey(Person)
176 delegate = models.ForeignKey(User, blank = True, null = True)
177 state = models.ForeignKey(State)
178 archived = models.BooleanField(default = False)
179 headers = models.TextField(blank = True)
180 content = models.TextField()
181 commit_ref = models.CharField(max_length=255, null = True, blank = True)
182 hash = HashField(null = True, db_index = True)
188 return Comment.objects.filter(patch = self)
194 self.state = State.objects.get(ordering = 0)
196 if self.hash is None:
197 self.hash = hash_patch(self.content).hexdigest()
199 super(Patch, self).save()
201 def is_editable(self, user):
202 if not user.is_authenticated():
205 if self.submitter.user == user or self.delegate == user:
208 profile = user.get_profile()
209 return self.project in user.get_profile().maintainer_projects.all()
212 f = PatchForm(instance = self, prefix = self.id)
216 fname_re = re.compile('[^-_A-Za-z0-9\.]+')
217 str = fname_re.sub('-', self.name)
218 return str.strip('-') + '.patch'
223 comment = Comment.objects.get(msgid = self.msgid)
229 body = comment.content.strip() + "\n\n"
232 mail = MIMEText(body)
233 mail['Subject'] = self.name
234 mail['Date'] = email.utils.formatdate(
235 time.mktime(self.date.utctimetuple()))
236 mail['From'] = str(self.submitter)
237 mail['X-Patchwork-Id'] = str(self.id)
238 mail.set_unixfrom('From patchwork ' + self.date.ctime())
244 def get_absolute_url(self):
245 return ('patchwork.views.patch.patch', (), {'patch_id': self.id})
248 verbose_name_plural = 'Patches'
251 class Comment(models.Model):
252 patch = models.ForeignKey(Patch)
253 msgid = models.CharField(max_length=255, unique = True)
254 submitter = models.ForeignKey(Person)
255 date = models.DateTimeField(default = datetime.datetime.now)
256 headers = models.TextField(blank = True)
257 content = models.TextField()
262 class Bundle(models.Model):
263 owner = models.ForeignKey(User)
264 project = models.ForeignKey(Project)
265 name = models.CharField(max_length = 50, null = False, blank = False)
266 patches = models.ManyToManyField(Patch)
267 public = models.BooleanField(default = False)
270 return self.patches.all().count()
273 unique_together = [('owner', 'name')]
275 def public_url(self):
278 site = Site.objects.get_current()
279 return 'http://%s%s' % (site.domain,
280 reverse('patchwork.views.bundle.public',
282 'username': self.owner.username,
283 'bundlename': self.name
287 return '\n'.join([p.mbox().as_string(True) \
288 for p in self.patches.all()])
290 class UserPersonConfirmation(models.Model):
291 user = models.ForeignKey(User)
292 email = models.CharField(max_length = 200)
294 date = models.DateTimeField(default=datetime.datetime.now)
295 active = models.BooleanField(default = True)
302 person = Person.objects.get(email = self.email)
306 person = Person(email = self.email)
308 person.link_to_user(self.user)
315 str = '%s%s%d' % (self.user, self.email, random.randint(0, max))
316 self.key = self._meta.get_field('key').construct(str).hexdigest()
317 super(UserPersonConfirmation, self).save()