]> git.ozlabs.org Git - patchwork/blob - apps/patchwork/models.py
e3fc9c7e1f7b068e0bb176c8d83ac35fb8d1873a
[patchwork] / apps / patchwork / models.py
1 # Patchwork - automated patch tracking system
2 # Copyright (C) 2008 Jeremy Kerr <jk@ozlabs.org>
3 #
4 # This file is part of the Patchwork package.
5 #
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.
10 #
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.
15 #
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
19
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
26
27 import re
28 import datetime, time
29 import string
30 import random
31 import hashlib
32 from email.mime.text import MIMEText
33 import email.utils
34
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)
39
40     def __str__(self):
41         if self.name:
42             return '%s <%s>' % (self.name, self.email)
43         else:
44             return self.email
45
46     def link_to_user(self, user):
47         self.name = user.get_profile().name()
48         self.user = user
49
50     class Meta:
51         verbose_name_plural = 'People'
52
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)
58
59     def __str__(self):
60         return self.name
61
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')
73
74     def name(self):
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
79
80     def contributor_projects(self):
81         submitters = Person.objects.filter(user = self.user)
82         return Project.objects \
83             .filter(id__in = \
84                     Patch.objects.filter(
85                         submitter__in = submitters) \
86                     .values('project_id').query)
87
88
89     def sync_person(self):
90         pass
91
92     def n_todo_patches(self):
93         return self.todo_patches().count()
94
95     def todo_patches(self, project = None):
96
97         # filter on project, if necessary
98         if project:
99             qs = Patch.objects.filter(project = project)
100         else:
101             qs = Patch.objects
102
103         qs = qs.filter(archived = False) \
104              .filter(delegate = self.user) \
105              .filter(state__in = \
106                      State.objects.filter(action_required = True) \
107                          .values('pk').query)
108         return qs
109
110     def save(self):
111         super(UserProfile, self).save()
112         people = Person.objects.filter(email = self.user.email)
113         if not people:
114             person = Person(email = self.user.email,
115                     name = self.name(), user = self.user)
116             person.save()
117         else:
118             for person in people:
119                  person.user = self.user
120                  person.save()
121
122     def __str__(self):
123         return self.name()
124
125 def _confirm_key():
126     allowedchars = string.ascii_lowercase + string.digits
127     str = ''
128     for i in range(1, 32):
129         str += random.choice(allowedchars)
130     return str;
131
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)
141
142     def create_user(self):
143         if not self.active:
144             return
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
149         user.save()
150         profile = UserProfile(user = user)
151         profile.save()
152         self.active = False
153         self.save()
154
155         # link a person to this user. if none exists, create.
156         person = None
157         try:
158             person = Person.objects.get(email = user.email)
159         except Exception:
160             pass
161         if not person:
162             person = Person(email = user.email)
163
164         person.link_to_user(user)
165         person.save()
166
167         return user
168
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)
175
176     def confirm(self):
177         if not self.active:
178             return
179         person = None
180         try:
181             person = Person.objects.get(email = self.email)
182         except Exception:
183             pass
184         if not person:
185             person = Person(email = self.email)
186
187         person.link_to_user(self.user)
188         person.save()
189         self.active = False
190
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)
195
196     def __str__(self):
197         return self.name
198
199     class Meta:
200         ordering = ['ordering']
201
202 class HashField(models.Field):
203     __metaclass__ = models.SubfieldBase
204
205     def __init__(self, algorithm = 'sha1', *args, **kwargs):
206         self.algorithm = algorithm
207         super(HashField, self).__init__(*args, **kwargs)
208
209     def db_type(self):
210         n_bytes = len(hashlib.new(self.algorithm).digest())
211         if settings.DATABASE_ENGINE == 'postgresql':
212             return 'bytea'
213         elif settings.DATABASE_ENGINE == 'mysql':
214             return 'binary(%d)' % n_bytes
215
216     def to_python(self, value):
217         return value
218
219     def get_db_prep_save(self, value):
220         return ''.join(map(lambda x: '\\%03o' % ord(x), value))
221
222     def get_manipulator_field_objs(self):
223         return [oldforms.TextField]
224
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)
237     hash = HashField()
238
239     def __str__(self):
240         return self.name
241
242     def comments(self):
243         return Comment.objects.filter(patch = self)
244
245     def save(self):
246         try:
247             s = self.state
248         except:
249             self.state = State.objects.get(ordering =  0)
250         if hash is None:
251             print "no hash"
252         super(Patch, self).save()
253
254     def is_editable(self, user):
255         if not user.is_authenticated():
256             return False
257
258         if self.submitter.user == user or self.delegate == user:
259             return True
260
261         profile = user.get_profile()
262         return self.project in user.get_profile().maintainer_projects.all()
263
264     def form(self):
265         f = PatchForm(instance = self, prefix = self.id)
266         return f
267
268     def filename(self):
269         fname_re = re.compile('[^-_A-Za-z0-9\.]+')
270         str = fname_re.sub('-', self.name)
271         return str.strip('-') + '.patch'
272
273     def mbox(self):
274         comment = None
275         try:
276             comment = Comment.objects.get(msgid = self.msgid)
277         except Exception:
278             pass
279
280         body = ''
281         if comment:
282             body = comment.content.strip() + "\n\n"
283         body += self.content
284
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())
292
293         return mail
294
295
296     @models.permalink
297     def get_absolute_url(self):
298         return ('patchwork.views.patch.patch', (), {'patch_id': self.id})
299
300     class Meta:
301         verbose_name_plural = 'Patches'
302         ordering = ['date']
303
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()
311
312     class Meta:
313         ordering = ['date']
314
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)
321
322     def n_patches(self):
323         return self.patches.all().count()
324
325     class Meta:
326         unique_together = [('owner', 'name')]
327
328     def public_url(self):
329         if not self.public:
330             return None
331         site = Site.objects.get_current()
332         return 'http://%s%s' % (site.domain,
333                 reverse('patchwork.views.bundle.public',
334                         kwargs = {
335                                 'username': self.owner.username,
336                                 'bundlename': self.name
337                         }))
338
339     def mbox(self):
340         return '\n'.join([p.mbox().as_string(True) \
341                         for p in self.patches.all()])
342