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'
56 class Project(models.Model):
57 linkname = models.CharField(max_length=255, unique=True)
58 name = models.CharField(max_length=255, unique=True)
59 listid = models.CharField(max_length=255, unique=True)
60 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
135 allowedchars = string.ascii_lowercase + string.digits
137 for i in range(1, 32):
138 str += random.choice(allowedchars)
141 class RegistrationRequest(models.Model):
142 username = models.CharField(max_length = 30, unique = True)
143 first_name = models.CharField(max_length = 50)
144 last_name = models.CharField(max_length = 50)
145 email = models.CharField(max_length = 200, unique = True)
146 password = models.CharField(max_length = 200)
147 key = models.CharField(max_length = 32, default = _confirm_key)
148 date = models.DateTimeField(default=datetime.datetime.now)
149 active = models.BooleanField(default = True)
151 def create_user(self):
154 user = User.objects.create_user(self.username,
155 self.email, self.password)
156 user.first_name = self.first_name
157 user.last_name = self.last_name
159 profile = UserProfile(user = user)
164 # link a person to this user. if none exists, create.
167 person = Person.objects.get(email = user.email)
171 person = Person(email = user.email)
173 person.link_to_user(user)
181 class UserPersonConfirmation(models.Model):
182 user = models.ForeignKey(User)
183 email = models.CharField(max_length = 200)
184 key = models.CharField(max_length = 32, default = _confirm_key)
185 date = models.DateTimeField(default=datetime.datetime.now)
186 active = models.BooleanField(default = True)
193 person = Person.objects.get(email = self.email)
197 person = Person(email = self.email)
199 person.link_to_user(self.user)
208 class State(models.Model):
209 name = models.CharField(max_length = 100)
210 ordering = models.IntegerField(unique = True)
211 action_required = models.BooleanField(default = True)
217 ordering = ['ordering']
222 class HashField(models.Field):
223 __metaclass__ = models.SubfieldBase
225 def __init__(self, algorithm = 'sha1', *args, **kwargs):
226 self.algorithm = algorithm
227 super(HashField, self).__init__(*args, **kwargs)
230 n_bytes = len(hashlib.new(self.algorithm).digest())
231 if settings.DATABASE_ENGINE == 'postgresql':
233 elif settings.DATABASE_ENGINE == 'mysql':
234 return 'binary(%d)' % n_bytes
236 def to_python(self, value):
239 def get_db_prep_save(self, value):
240 return ''.join(map(lambda x: '\\%03o' % ord(x), value))
242 def get_manipulator_field_objs(self):
243 return [oldforms.TextField]
245 class Patch(models.Model):
246 project = models.ForeignKey(Project)
247 msgid = models.CharField(max_length=255, unique = True)
248 name = models.CharField(max_length=255)
249 date = models.DateTimeField(default=datetime.datetime.now)
250 submitter = models.ForeignKey(Person)
251 delegate = models.ForeignKey(User, blank = True, null = True)
252 state = models.ForeignKey(State)
253 archived = models.BooleanField(default = False)
254 headers = models.TextField(blank = True)
255 content = models.TextField()
256 commit_ref = models.CharField(max_length=255, null = True, blank = True)
263 return Comment.objects.filter(patch = self)
269 self.state = State.objects.get(ordering = 0)
272 super(Patch, self).save()
274 def is_editable(self, user):
275 if not user.is_authenticated():
278 if self.submitter.user == user or self.delegate == user:
281 profile = user.get_profile()
282 return self.project in user.get_profile().maintainer_projects.all()
285 f = PatchForm(instance = self, prefix = self.id)
289 fname_re = re.compile('[^-_A-Za-z0-9\.]+')
290 str = fname_re.sub('-', self.name)
291 return str.strip('-') + '.patch'
296 comment = Comment.objects.get(msgid = self.msgid)
302 body = comment.content.strip() + "\n\n"
305 mail = MIMEText(body)
306 mail['Subject'] = self.name
307 mail['Date'] = email.utils.formatdate(
308 time.mktime(self.date.utctimetuple()))
309 mail['From'] = str(self.submitter)
310 mail['X-Patchwork-Id'] = str(self.id)
311 mail.set_unixfrom('From patchwork ' + self.date.ctime())
317 def get_absolute_url(self):
318 return ('patchwork.views.patch.patch', (), {'patch_id': self.id})
321 verbose_name_plural = 'Patches'
327 class Comment(models.Model):
328 patch = models.ForeignKey(Patch)
329 msgid = models.CharField(max_length=255, unique = True)
330 submitter = models.ForeignKey(Person)
331 date = models.DateTimeField(default = datetime.datetime.now)
332 headers = models.TextField(blank = True)
333 content = models.TextField()
341 class Bundle(models.Model):
342 owner = models.ForeignKey(User)
343 project = models.ForeignKey(Project)
344 name = models.CharField(max_length = 50, null = False, blank = False)
345 patches = models.ManyToManyField(Patch)
346 public = models.BooleanField(default = False)
349 return self.patches.all().count()
352 unique_together = [('owner', 'name')]
357 def public_url(self):
360 site = Site.objects.get_current()
361 return 'http://%s%s' % (site.domain,
362 reverse('patchwork.views.bundle.public',
364 'username': self.owner.username,
365 'bundlename': self.name
369 return '\n'.join([p.mbox().as_string(True) \
370 for p in self.patches.all()])