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 import django.oldforms as oldforms
33 from email.mime.text import MIMEText
36 # Python 2.4 compatibility
37 from email.MIMEText import MIMEText
39 email.utils = email.Utils
41 class Person(models.Model):
42 email = models.CharField(max_length=255, unique = True)
43 name = models.CharField(max_length=255, null = True)
44 user = models.ForeignKey(User, null = True)
48 return '%s <%s>' % (self.name, self.email)
52 def link_to_user(self, user):
53 self.name = user.get_profile().name()
57 verbose_name_plural = 'People'
59 class Project(models.Model):
60 linkname = models.CharField(max_length=255, unique=True)
61 name = models.CharField(max_length=255, unique=True)
62 listid = models.CharField(max_length=255, unique=True)
63 listemail = models.CharField(max_length=200)
68 class UserProfile(models.Model):
69 user = models.ForeignKey(User, unique = True)
70 primary_project = models.ForeignKey(Project, null = True)
71 maintainer_projects = models.ManyToManyField(Project,
72 related_name = 'maintainer_project')
73 send_email = models.BooleanField(default = False,
74 help_text = 'Selecting this option allows patchwork to send ' +
75 'email on your behalf')
76 patches_per_page = models.PositiveIntegerField(default = 100,
77 null = False, blank = False,
78 help_text = 'Number of patches to display per page')
81 if self.user.first_name or self.user.last_name:
82 names = filter(bool, [self.user.first_name, self.user.last_name])
83 return ' '.join(names)
84 return self.user.username
86 def contributor_projects(self):
87 submitters = Person.objects.filter(user = self.user)
88 return Project.objects \
91 submitter__in = submitters) \
92 .values('project_id').query)
95 def sync_person(self):
98 def n_todo_patches(self):
99 return self.todo_patches().count()
101 def todo_patches(self, project = None):
103 # filter on project, if necessary
105 qs = Patch.objects.filter(project = project)
109 qs = qs.filter(archived = False) \
110 .filter(delegate = self.user) \
111 .filter(state__in = \
112 State.objects.filter(action_required = True) \
117 super(UserProfile, self).save()
118 people = Person.objects.filter(email = self.user.email)
120 person = Person(email = self.user.email,
121 name = self.name(), user = self.user)
124 for person in people:
125 person.user = self.user
132 allowedchars = string.ascii_lowercase + string.digits
134 for i in range(1, 32):
135 str += random.choice(allowedchars)
138 class UserPersonConfirmation(models.Model):
139 user = models.ForeignKey(User)
140 email = models.CharField(max_length = 200)
141 key = models.CharField(max_length = 32, default = _confirm_key)
142 date = models.DateTimeField(default=datetime.datetime.now)
143 active = models.BooleanField(default = True)
150 person = Person.objects.get(email = self.email)
154 person = Person(email = self.email)
156 person.link_to_user(self.user)
160 class State(models.Model):
161 name = models.CharField(max_length = 100)
162 ordering = models.IntegerField(unique = True)
163 action_required = models.BooleanField(default = True)
169 ordering = ['ordering']
171 class HashField(models.Field):
172 __metaclass__ = models.SubfieldBase
174 def __init__(self, algorithm = 'sha1', *args, **kwargs):
175 self.algorithm = algorithm
181 if algorithm == 'sha1':
183 self.hash_constructor = sha.new
184 elif algorithm == 'md5':
186 self.hash_constructor = md5.new
188 raise NameError("Unknown algorithm '%s'" % algorithm)
190 super(HashField, self).__init__(*args, **kwargs)
194 n_bytes = len(hashlib.new(self.algorithm).digest())
196 n_bytes = len(self.hash_constructor().digest())
197 if settings.DATABASE_ENGINE == 'postgresql':
199 elif settings.DATABASE_ENGINE == 'mysql':
200 return 'binary(%d)' % n_bytes
202 def to_python(self, value):
205 def get_db_prep_save(self, value):
206 return ''.join(map(lambda x: '\\%03o' % ord(x), value))
208 def get_manipulator_field_objs(self):
209 return [oldforms.TextField]
211 class Patch(models.Model):
212 project = models.ForeignKey(Project)
213 msgid = models.CharField(max_length=255, unique = True)
214 name = models.CharField(max_length=255)
215 date = models.DateTimeField(default=datetime.datetime.now)
216 submitter = models.ForeignKey(Person)
217 delegate = models.ForeignKey(User, blank = True, null = True)
218 state = models.ForeignKey(State)
219 archived = models.BooleanField(default = False)
220 headers = models.TextField(blank = True)
221 content = models.TextField()
222 commit_ref = models.CharField(max_length=255, null = True, blank = True)
229 return Comment.objects.filter(patch = self)
235 self.state = State.objects.get(ordering = 0)
238 super(Patch, self).save()
240 def is_editable(self, user):
241 if not user.is_authenticated():
244 if self.submitter.user == user or self.delegate == user:
247 profile = user.get_profile()
248 return self.project in user.get_profile().maintainer_projects.all()
251 f = PatchForm(instance = self, prefix = self.id)
255 fname_re = re.compile('[^-_A-Za-z0-9\.]+')
256 str = fname_re.sub('-', self.name)
257 return str.strip('-') + '.patch'
262 comment = Comment.objects.get(msgid = self.msgid)
268 body = comment.content.strip() + "\n\n"
271 mail = MIMEText(body)
272 mail['Subject'] = self.name
273 mail['Date'] = email.utils.formatdate(
274 time.mktime(self.date.utctimetuple()))
275 mail['From'] = str(self.submitter)
276 mail['X-Patchwork-Id'] = str(self.id)
277 mail.set_unixfrom('From patchwork ' + self.date.ctime())
283 def get_absolute_url(self):
284 return ('patchwork.views.patch.patch', (), {'patch_id': self.id})
287 verbose_name_plural = 'Patches'
290 class Comment(models.Model):
291 patch = models.ForeignKey(Patch)
292 msgid = models.CharField(max_length=255, unique = True)
293 submitter = models.ForeignKey(Person)
294 date = models.DateTimeField(default = datetime.datetime.now)
295 headers = models.TextField(blank = True)
296 content = models.TextField()
301 class Bundle(models.Model):
302 owner = models.ForeignKey(User)
303 project = models.ForeignKey(Project)
304 name = models.CharField(max_length = 50, null = False, blank = False)
305 patches = models.ManyToManyField(Patch)
306 public = models.BooleanField(default = False)
309 return self.patches.all().count()
312 unique_together = [('owner', 'name')]
314 def public_url(self):
317 site = Site.objects.get_current()
318 return 'http://%s%s' % (site.domain,
319 reverse('patchwork.views.bundle.public',
321 'username': self.owner.username,
322 'bundlename': self.name
326 return '\n'.join([p.mbox().as_string(True) \
327 for p in self.patches.all()])