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)
149 def create_user(self):
150 user = User.objects.create_user(self.username,
151 self.email, self.password)
152 user.first_name = self.first_name
153 user.last_name = self.last_name
155 profile = UserProfile(user = user)
159 # link a person to this user. if none exists, create.
162 person = Person.objects.get(email = user.email)
166 person = Person(email = user.email)
168 person.link_to_user(user)
176 class UserPersonConfirmation(models.Model):
177 user = models.ForeignKey(User)
178 email = models.CharField(max_length = 200)
179 date = models.DateTimeField(default=datetime.datetime.now)
180 key = models.CharField(max_length = 32, default = _confirm_key)
185 person = Person.objects.get(email = self.email)
189 person = Person(email = self.email)
191 person.link_to_user(self.user)
199 class State(models.Model):
200 name = models.CharField(max_length = 100)
201 ordering = models.IntegerField(unique = True)
202 action_required = models.BooleanField(default = True)
208 ordering = ['ordering']
213 class HashField(models.Field):
214 __metaclass__ = models.SubfieldBase
216 def __init__(self, algorithm = 'sha1', *args, **kwargs):
217 self.algorithm = algorithm
218 super(HashField, self).__init__(*args, **kwargs)
221 n_bytes = len(hashlib.new(self.algorithm).digest())
222 if settings.DATABASE_ENGINE == 'postgresql':
224 elif settings.DATABASE_ENGINE == 'mysql':
225 return 'binary(%d)' % n_bytes
227 def to_python(self, value):
230 def get_db_prep_save(self, value):
231 return ''.join(map(lambda x: '\\%03o' % ord(x), value))
233 def get_manipulator_field_objs(self):
234 return [oldforms.TextField]
236 class Patch(models.Model):
237 project = models.ForeignKey(Project)
238 msgid = models.CharField(max_length=255, unique = True)
239 name = models.CharField(max_length=255)
240 date = models.DateTimeField(default=datetime.datetime.now)
241 submitter = models.ForeignKey(Person)
242 delegate = models.ForeignKey(User, blank = True, null = True)
243 state = models.ForeignKey(State)
244 archived = models.BooleanField(default = False)
245 headers = models.TextField(blank = True)
246 content = models.TextField()
247 commit_ref = models.CharField(max_length=255, null = True, blank = True)
254 return Comment.objects.filter(patch = self)
260 self.state = State.objects.get(ordering = 0)
263 super(Patch, self).save()
265 def is_editable(self, user):
266 if not user.is_authenticated():
269 if self.submitter.user == user or self.delegate == user:
272 profile = user.get_profile()
273 return self.project in user.get_profile().maintainer_projects.all()
276 f = PatchForm(instance = self, prefix = self.id)
280 fname_re = re.compile('[^-_A-Za-z0-9\.]+')
281 str = fname_re.sub('-', self.name)
282 return str.strip('-') + '.patch'
287 comment = Comment.objects.get(msgid = self.msgid)
293 body = comment.content.strip() + "\n\n"
296 mail = MIMEText(body)
297 mail['Subject'] = self.name
298 mail['Date'] = email.utils.formatdate(
299 time.mktime(self.date.utctimetuple()))
300 mail['From'] = str(self.submitter)
301 mail['X-Patchwork-Id'] = str(self.id)
302 mail.set_unixfrom('From patchwork ' + self.date.ctime())
308 def get_absolute_url(self):
309 return ('patchwork.views.patch.patch', (), {'patch_id': self.id})
312 verbose_name_plural = 'Patches'
318 class Comment(models.Model):
319 patch = models.ForeignKey(Patch)
320 msgid = models.CharField(max_length=255, unique = True)
321 submitter = models.ForeignKey(Person)
322 date = models.DateTimeField(default = datetime.datetime.now)
323 headers = models.TextField(blank = True)
324 content = models.TextField()
332 class Bundle(models.Model):
333 owner = models.ForeignKey(User)
334 project = models.ForeignKey(Project)
335 name = models.CharField(max_length = 50, null = False, blank = False)
336 patches = models.ManyToManyField(Patch)
337 public = models.BooleanField(default = False)
340 return self.patches.all().count()
343 unique_together = [('owner', 'name')]
348 def public_url(self):
351 site = Site.objects.get_current()
352 return 'http://%s%s' % (site.domain,
353 reverse('patchwork.views.bundle.public',
355 'username': self.owner.username,
356 'bundlename': self.name
360 return '\n'.join([p.mbox().as_string(True) \
361 for p in self.patches.all()])