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
32 from email.mime.text import MIMEText
35 class Person(models.Model):
36 email = models.CharField(max_length=255, unique = True)
37 name = models.CharField(max_length=255, null = True)
38 user = models.ForeignKey(User, null = True)
42 return '%s <%s>' % (self.name, self.email)
46 def link_to_user(self, user):
47 self.name = user.get_profile().name()
51 verbose_name_plural = 'People'
53 class Project(models.Model):
54 linkname = models.CharField(max_length=255, unique=True)
55 name = models.CharField(max_length=255, unique=True)
56 listid = models.CharField(max_length=255, unique=True)
57 listemail = models.CharField(max_length=200)
62 class UserProfile(models.Model):
63 user = models.ForeignKey(User, unique = True)
64 primary_project = models.ForeignKey(Project, null = True)
65 maintainer_projects = models.ManyToManyField(Project,
66 related_name = 'maintainer_project')
67 send_email = models.BooleanField(default = False,
68 help_text = 'Selecting this option allows patchwork to send ' +
69 'email on your behalf')
70 patches_per_page = models.PositiveIntegerField(default = 100,
71 null = False, blank = False,
72 help_text = 'Number of patches to display per page')
75 if self.user.first_name or self.user.last_name:
76 names = filter(bool, [self.user.first_name, self.user.last_name])
77 return ' '.join(names)
78 return self.user.username
80 def contributor_projects(self):
81 submitters = Person.objects.filter(user = self.user)
82 return Project.objects \
85 submitter__in = submitters) \
86 .values('project_id').query)
89 def sync_person(self):
92 def n_todo_patches(self):
93 return self.todo_patches().count()
95 def todo_patches(self, project = None):
97 # filter on project, if necessary
99 qs = Patch.objects.filter(project = project)
103 qs = qs.filter(archived = False) \
104 .filter(delegate = self.user) \
105 .filter(state__in = \
106 State.objects.filter(action_required = True) \
111 super(UserProfile, self).save()
112 people = Person.objects.filter(email = self.user.email)
114 person = Person(email = self.user.email,
115 name = self.name(), user = self.user)
118 for person in people:
119 person.user = self.user
126 allowedchars = string.ascii_lowercase + string.digits
128 for i in range(1, 32):
129 str += random.choice(allowedchars)
132 class UserPersonConfirmation(models.Model):
133 user = models.ForeignKey(User)
134 email = models.CharField(max_length = 200)
135 key = models.CharField(max_length = 32, default = _confirm_key)
136 date = models.DateTimeField(default=datetime.datetime.now)
137 active = models.BooleanField(default = True)
144 person = Person.objects.get(email = self.email)
148 person = Person(email = self.email)
150 person.link_to_user(self.user)
154 class State(models.Model):
155 name = models.CharField(max_length = 100)
156 ordering = models.IntegerField(unique = True)
157 action_required = models.BooleanField(default = True)
163 ordering = ['ordering']
165 class HashField(models.Field):
166 __metaclass__ = models.SubfieldBase
168 def __init__(self, algorithm = 'sha1', *args, **kwargs):
169 self.algorithm = algorithm
170 super(HashField, self).__init__(*args, **kwargs)
173 n_bytes = len(hashlib.new(self.algorithm).digest())
174 if settings.DATABASE_ENGINE == 'postgresql':
176 elif settings.DATABASE_ENGINE == 'mysql':
177 return 'binary(%d)' % n_bytes
179 def to_python(self, value):
182 def get_db_prep_save(self, value):
183 return ''.join(map(lambda x: '\\%03o' % ord(x), value))
185 def get_manipulator_field_objs(self):
186 return [oldforms.TextField]
188 class Patch(models.Model):
189 project = models.ForeignKey(Project)
190 msgid = models.CharField(max_length=255, unique = True)
191 name = models.CharField(max_length=255)
192 date = models.DateTimeField(default=datetime.datetime.now)
193 submitter = models.ForeignKey(Person)
194 delegate = models.ForeignKey(User, blank = True, null = True)
195 state = models.ForeignKey(State)
196 archived = models.BooleanField(default = False)
197 headers = models.TextField(blank = True)
198 content = models.TextField()
199 commit_ref = models.CharField(max_length=255, null = True, blank = True)
206 return Comment.objects.filter(patch = self)
212 self.state = State.objects.get(ordering = 0)
215 super(Patch, self).save()
217 def is_editable(self, user):
218 if not user.is_authenticated():
221 if self.submitter.user == user or self.delegate == user:
224 profile = user.get_profile()
225 return self.project in user.get_profile().maintainer_projects.all()
228 f = PatchForm(instance = self, prefix = self.id)
232 fname_re = re.compile('[^-_A-Za-z0-9\.]+')
233 str = fname_re.sub('-', self.name)
234 return str.strip('-') + '.patch'
239 comment = Comment.objects.get(msgid = self.msgid)
245 body = comment.content.strip() + "\n\n"
248 mail = MIMEText(body)
249 mail['Subject'] = self.name
250 mail['Date'] = email.utils.formatdate(
251 time.mktime(self.date.utctimetuple()))
252 mail['From'] = str(self.submitter)
253 mail['X-Patchwork-Id'] = str(self.id)
254 mail.set_unixfrom('From patchwork ' + self.date.ctime())
260 def get_absolute_url(self):
261 return ('patchwork.views.patch.patch', (), {'patch_id': self.id})
264 verbose_name_plural = 'Patches'
267 class Comment(models.Model):
268 patch = models.ForeignKey(Patch)
269 msgid = models.CharField(max_length=255, unique = True)
270 submitter = models.ForeignKey(Person)
271 date = models.DateTimeField(default = datetime.datetime.now)
272 headers = models.TextField(blank = True)
273 content = models.TextField()
278 class Bundle(models.Model):
279 owner = models.ForeignKey(User)
280 project = models.ForeignKey(Project)
281 name = models.CharField(max_length = 50, null = False, blank = False)
282 patches = models.ManyToManyField(Patch)
283 public = models.BooleanField(default = False)
286 return self.patches.all().count()
289 unique_together = [('owner', 'name')]
291 def public_url(self):
294 site = Site.objects.get_current()
295 return 'http://%s%s' % (site.domain,
296 reverse('patchwork.views.bundle.public',
298 'username': self.owner.username,
299 'bundlename': self.name
303 return '\n'.join([p.mbox().as_string(True) \
304 for p in self.patches.all()])