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.startswith('postgresql'):
199 elif settings.DATABASE_ENGINE == 'mysql':
200 return 'binary(%d)' % n_bytes
202 raise Exception("Unknown database engine '%s'" % \
203 settings.DATABASE_ENGINE)
205 def to_python(self, value):
208 def get_db_prep_save(self, value):
209 return ''.join(map(lambda x: '\\%03o' % ord(x), value))
211 def get_manipulator_field_objs(self):
212 return [oldforms.TextField]
214 class Patch(models.Model):
215 project = models.ForeignKey(Project)
216 msgid = models.CharField(max_length=255, unique = True)
217 name = models.CharField(max_length=255)
218 date = models.DateTimeField(default=datetime.datetime.now)
219 submitter = models.ForeignKey(Person)
220 delegate = models.ForeignKey(User, blank = True, null = True)
221 state = models.ForeignKey(State)
222 archived = models.BooleanField(default = False)
223 headers = models.TextField(blank = True)
224 content = models.TextField()
225 commit_ref = models.CharField(max_length=255, null = True, blank = True)
232 return Comment.objects.filter(patch = self)
238 self.state = State.objects.get(ordering = 0)
241 super(Patch, self).save()
243 def is_editable(self, user):
244 if not user.is_authenticated():
247 if self.submitter.user == user or self.delegate == user:
250 profile = user.get_profile()
251 return self.project in user.get_profile().maintainer_projects.all()
254 f = PatchForm(instance = self, prefix = self.id)
258 fname_re = re.compile('[^-_A-Za-z0-9\.]+')
259 str = fname_re.sub('-', self.name)
260 return str.strip('-') + '.patch'
265 comment = Comment.objects.get(msgid = self.msgid)
271 body = comment.content.strip() + "\n\n"
274 mail = MIMEText(body)
275 mail['Subject'] = self.name
276 mail['Date'] = email.utils.formatdate(
277 time.mktime(self.date.utctimetuple()))
278 mail['From'] = str(self.submitter)
279 mail['X-Patchwork-Id'] = str(self.id)
280 mail.set_unixfrom('From patchwork ' + self.date.ctime())
286 def get_absolute_url(self):
287 return ('patchwork.views.patch.patch', (), {'patch_id': self.id})
290 verbose_name_plural = 'Patches'
293 class Comment(models.Model):
294 patch = models.ForeignKey(Patch)
295 msgid = models.CharField(max_length=255, unique = True)
296 submitter = models.ForeignKey(Person)
297 date = models.DateTimeField(default = datetime.datetime.now)
298 headers = models.TextField(blank = True)
299 content = models.TextField()
304 class Bundle(models.Model):
305 owner = models.ForeignKey(User)
306 project = models.ForeignKey(Project)
307 name = models.CharField(max_length = 50, null = False, blank = False)
308 patches = models.ManyToManyField(Patch)
309 public = models.BooleanField(default = False)
312 return self.patches.all().count()
315 unique_together = [('owner', 'name')]
317 def public_url(self):
320 site = Site.objects.get_current()
321 return 'http://%s%s' % (site.domain,
322 reverse('patchwork.views.bundle.public',
324 'username': self.owner.username,
325 'bundlename': self.name
329 return '\n'.join([p.mbox().as_string(True) \
330 for p in self.patches.all()])