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
31 from email.mime.text import MIMEText
34 class Person(models.Model):
35 email = models.CharField(max_length=255, unique = True)
36 name = models.CharField(max_length=255, null = True)
37 user = models.ForeignKey(User, null = True)
41 return '%s <%s>' % (self.name, self.email)
45 def link_to_user(self, user):
46 self.name = user.get_profile().name()
50 verbose_name_plural = 'People'
52 class Project(models.Model):
53 linkname = models.CharField(max_length=255, unique=True)
54 name = models.CharField(max_length=255, unique=True)
55 listid = models.CharField(max_length=255, unique=True)
56 listemail = models.CharField(max_length=200)
61 class UserProfile(models.Model):
62 user = models.ForeignKey(User, unique = True)
63 primary_project = models.ForeignKey(Project, null = True)
64 maintainer_projects = models.ManyToManyField(Project,
65 related_name = 'maintainer_project')
66 send_email = models.BooleanField(default = False,
67 help_text = 'Selecting this option allows patchwork to send ' +
68 'email on your behalf')
69 patches_per_page = models.PositiveIntegerField(default = 100,
70 null = False, blank = False,
71 help_text = 'Number of patches to display per page')
74 if self.user.first_name or self.user.last_name:
75 names = filter(bool, [self.user.first_name, self.user.last_name])
76 return ' '.join(names)
77 return self.user.username
79 def contributor_projects(self):
80 submitters = Person.objects.filter(user = self.user)
81 return Project.objects \
84 submitter__in = submitters) \
85 .values('project_id').query)
88 def sync_person(self):
91 def n_todo_patches(self):
92 return self.todo_patches().count()
94 def todo_patches(self, project = None):
96 # filter on project, if necessary
98 qs = Patch.objects.filter(project = project)
102 qs = qs.filter(archived = False) \
103 .filter(delegate = self.user) \
104 .filter(state__in = \
105 State.objects.filter(action_required = True) \
110 super(UserProfile, self).save()
111 people = Person.objects.filter(email = self.user.email)
113 person = Person(email = self.user.email,
114 name = self.name(), user = self.user)
117 for person in people:
118 person.user = self.user
125 allowedchars = string.ascii_lowercase + string.digits
127 for i in range(1, 32):
128 str += random.choice(allowedchars)
131 class UserPersonConfirmation(models.Model):
132 user = models.ForeignKey(User)
133 email = models.CharField(max_length = 200)
134 key = models.CharField(max_length = 32, default = _confirm_key)
135 date = models.DateTimeField(default=datetime.datetime.now)
136 active = models.BooleanField(default = True)
143 person = Person.objects.get(email = self.email)
147 person = Person(email = self.email)
149 person.link_to_user(self.user)
153 class State(models.Model):
154 name = models.CharField(max_length = 100)
155 ordering = models.IntegerField(unique = True)
156 action_required = models.BooleanField(default = True)
162 ordering = ['ordering']
164 class HashField(models.Field):
165 __metaclass__ = models.SubfieldBase
167 def __init__(self, algorithm = 'sha1', *args, **kwargs):
168 self.algorithm = algorithm
174 if algorithm == 'sha1':
176 self.hash_constructor = sha.new
177 elif algorithm == 'md5':
179 self.hash_constructor = md5.new
181 raise NameError("Unknown algorithm '%s'" % algorithm)
183 super(HashField, self).__init__(*args, **kwargs)
187 n_bytes = len(hashlib.new(self.algorithm).digest())
189 n_bytes = len(self.hash_constructor().digest())
190 if settings.DATABASE_ENGINE == 'postgresql':
192 elif settings.DATABASE_ENGINE == 'mysql':
193 return 'binary(%d)' % n_bytes
195 def to_python(self, value):
198 def get_db_prep_save(self, value):
199 return ''.join(map(lambda x: '\\%03o' % ord(x), value))
201 def get_manipulator_field_objs(self):
202 return [oldforms.TextField]
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)
222 return Comment.objects.filter(patch = self)
228 self.state = State.objects.get(ordering = 0)
231 super(Patch, self).save()
233 def is_editable(self, user):
234 if not user.is_authenticated():
237 if self.submitter.user == user or self.delegate == user:
240 profile = user.get_profile()
241 return self.project in user.get_profile().maintainer_projects.all()
244 f = PatchForm(instance = self, prefix = self.id)
248 fname_re = re.compile('[^-_A-Za-z0-9\.]+')
249 str = fname_re.sub('-', self.name)
250 return str.strip('-') + '.patch'
255 comment = Comment.objects.get(msgid = self.msgid)
261 body = comment.content.strip() + "\n\n"
264 mail = MIMEText(body)
265 mail['Subject'] = self.name
266 mail['Date'] = email.utils.formatdate(
267 time.mktime(self.date.utctimetuple()))
268 mail['From'] = str(self.submitter)
269 mail['X-Patchwork-Id'] = str(self.id)
270 mail.set_unixfrom('From patchwork ' + self.date.ctime())
276 def get_absolute_url(self):
277 return ('patchwork.views.patch.patch', (), {'patch_id': self.id})
280 verbose_name_plural = 'Patches'
283 class Comment(models.Model):
284 patch = models.ForeignKey(Patch)
285 msgid = models.CharField(max_length=255, unique = True)
286 submitter = models.ForeignKey(Person)
287 date = models.DateTimeField(default = datetime.datetime.now)
288 headers = models.TextField(blank = True)
289 content = models.TextField()
294 class Bundle(models.Model):
295 owner = models.ForeignKey(User)
296 project = models.ForeignKey(Project)
297 name = models.CharField(max_length = 50, null = False, blank = False)
298 patches = models.ManyToManyField(Patch)
299 public = models.BooleanField(default = False)
302 return self.patches.all().count()
305 unique_together = [('owner', 'name')]
307 def public_url(self):
310 site = Site.objects.get_current()
311 return 'http://%s%s' % (site.domain,
312 reverse('patchwork.views.bundle.public',
314 'username': self.owner.username,
315 'bundlename': self.name
319 return '\n'.join([p.mbox().as_string(True) \
320 for p in self.patches.all()])