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 RegistrationRequest(models.Model):
133 username = models.CharField(max_length = 30, unique = True)
134 first_name = models.CharField(max_length = 50)
135 last_name = models.CharField(max_length = 50)
136 email = models.CharField(max_length = 200, unique = True)
137 password = models.CharField(max_length = 200)
138 key = models.CharField(max_length = 32, default = _confirm_key)
139 date = models.DateTimeField(default=datetime.datetime.now)
140 active = models.BooleanField(default = True)
142 def create_user(self):
145 user = User.objects.create_user(self.username,
146 self.email, self.password)
147 user.first_name = self.first_name
148 user.last_name = self.last_name
150 profile = UserProfile(user = user)
155 # link a person to this user. if none exists, create.
158 person = Person.objects.get(email = user.email)
162 person = Person(email = user.email)
164 person.link_to_user(user)
169 class UserPersonConfirmation(models.Model):
170 user = models.ForeignKey(User)
171 email = models.CharField(max_length = 200)
172 key = models.CharField(max_length = 32, default = _confirm_key)
173 date = models.DateTimeField(default=datetime.datetime.now)
174 active = models.BooleanField(default = True)
181 person = Person.objects.get(email = self.email)
185 person = Person(email = self.email)
187 person.link_to_user(self.user)
191 class State(models.Model):
192 name = models.CharField(max_length = 100)
193 ordering = models.IntegerField(unique = True)
194 action_required = models.BooleanField(default = True)
200 ordering = ['ordering']
202 class HashField(models.Field):
203 __metaclass__ = models.SubfieldBase
205 def __init__(self, algorithm = 'sha1', *args, **kwargs):
206 self.algorithm = algorithm
207 super(HashField, self).__init__(*args, **kwargs)
210 n_bytes = len(hashlib.new(self.algorithm).digest())
211 if settings.DATABASE_ENGINE == 'postgresql':
213 elif settings.DATABASE_ENGINE == 'mysql':
214 return 'binary(%d)' % n_bytes
216 def to_python(self, value):
219 def get_db_prep_save(self, value):
220 return ''.join(map(lambda x: '\\%03o' % ord(x), value))
222 def get_manipulator_field_objs(self):
223 return [oldforms.TextField]
225 class Patch(models.Model):
226 project = models.ForeignKey(Project)
227 msgid = models.CharField(max_length=255, unique = True)
228 name = models.CharField(max_length=255)
229 date = models.DateTimeField(default=datetime.datetime.now)
230 submitter = models.ForeignKey(Person)
231 delegate = models.ForeignKey(User, blank = True, null = True)
232 state = models.ForeignKey(State)
233 archived = models.BooleanField(default = False)
234 headers = models.TextField(blank = True)
235 content = models.TextField()
236 commit_ref = models.CharField(max_length=255, null = True, blank = True)
243 return Comment.objects.filter(patch = self)
249 self.state = State.objects.get(ordering = 0)
252 super(Patch, self).save()
254 def is_editable(self, user):
255 if not user.is_authenticated():
258 if self.submitter.user == user or self.delegate == user:
261 profile = user.get_profile()
262 return self.project in user.get_profile().maintainer_projects.all()
265 f = PatchForm(instance = self, prefix = self.id)
269 fname_re = re.compile('[^-_A-Za-z0-9\.]+')
270 str = fname_re.sub('-', self.name)
271 return str.strip('-') + '.patch'
276 comment = Comment.objects.get(msgid = self.msgid)
282 body = comment.content.strip() + "\n\n"
285 mail = MIMEText(body)
286 mail['Subject'] = self.name
287 mail['Date'] = email.utils.formatdate(
288 time.mktime(self.date.utctimetuple()))
289 mail['From'] = str(self.submitter)
290 mail['X-Patchwork-Id'] = str(self.id)
291 mail.set_unixfrom('From patchwork ' + self.date.ctime())
297 def get_absolute_url(self):
298 return ('patchwork.views.patch.patch', (), {'patch_id': self.id})
301 verbose_name_plural = 'Patches'
304 class Comment(models.Model):
305 patch = models.ForeignKey(Patch)
306 msgid = models.CharField(max_length=255, unique = True)
307 submitter = models.ForeignKey(Person)
308 date = models.DateTimeField(default = datetime.datetime.now)
309 headers = models.TextField(blank = True)
310 content = models.TextField()
315 class Bundle(models.Model):
316 owner = models.ForeignKey(User)
317 project = models.ForeignKey(Project)
318 name = models.CharField(max_length = 50, null = False, blank = False)
319 patches = models.ManyToManyField(Patch)
320 public = models.BooleanField(default = False)
323 return self.patches.all().count()
326 unique_together = [('owner', 'name')]
328 def public_url(self):
331 site = Site.objects.get_current()
332 return 'http://%s%s' % (site.domain,
333 reverse('patchwork.views.bundle.public',
335 'username': self.owner.username,
336 'bundlename': self.name
340 return '\n'.join([p.mbox().as_string(True) \
341 for p in self.patches.all()])