]> git.ozlabs.org Git - patchwork/blob - apps/patchwork/models.py
Use django-registration infrastructure
[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 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)
138
139     def confirm(self):
140         if not self.active:
141             return
142         person = None
143         try:
144             person = Person.objects.get(email = self.email)
145         except Exception:
146             pass
147         if not person:
148             person = Person(email = self.email)
149
150         person.link_to_user(self.user)
151         person.save()
152         self.active = False
153
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)
158
159     def __str__(self):
160         return self.name
161
162     class Meta:
163         ordering = ['ordering']
164
165 class HashField(models.Field):
166     __metaclass__ = models.SubfieldBase
167
168     def __init__(self, algorithm = 'sha1', *args, **kwargs):
169         self.algorithm = algorithm
170         super(HashField, self).__init__(*args, **kwargs)
171
172     def db_type(self):
173         n_bytes = len(hashlib.new(self.algorithm).digest())
174         if settings.DATABASE_ENGINE == 'postgresql':
175             return 'bytea'
176         elif settings.DATABASE_ENGINE == 'mysql':
177             return 'binary(%d)' % n_bytes
178
179     def to_python(self, value):
180         return value
181
182     def get_db_prep_save(self, value):
183         return ''.join(map(lambda x: '\\%03o' % ord(x), value))
184
185     def get_manipulator_field_objs(self):
186         return [oldforms.TextField]
187
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)
200     hash = HashField()
201
202     def __str__(self):
203         return self.name
204
205     def comments(self):
206         return Comment.objects.filter(patch = self)
207
208     def save(self):
209         try:
210             s = self.state
211         except:
212             self.state = State.objects.get(ordering =  0)
213         if hash is None:
214             print "no hash"
215         super(Patch, self).save()
216
217     def is_editable(self, user):
218         if not user.is_authenticated():
219             return False
220
221         if self.submitter.user == user or self.delegate == user:
222             return True
223
224         profile = user.get_profile()
225         return self.project in user.get_profile().maintainer_projects.all()
226
227     def form(self):
228         f = PatchForm(instance = self, prefix = self.id)
229         return f
230
231     def filename(self):
232         fname_re = re.compile('[^-_A-Za-z0-9\.]+')
233         str = fname_re.sub('-', self.name)
234         return str.strip('-') + '.patch'
235
236     def mbox(self):
237         comment = None
238         try:
239             comment = Comment.objects.get(msgid = self.msgid)
240         except Exception:
241             pass
242
243         body = ''
244         if comment:
245             body = comment.content.strip() + "\n\n"
246         body += self.content
247
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())
255
256         return mail
257
258
259     @models.permalink
260     def get_absolute_url(self):
261         return ('patchwork.views.patch.patch', (), {'patch_id': self.id})
262
263     class Meta:
264         verbose_name_plural = 'Patches'
265         ordering = ['date']
266
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()
274
275     class Meta:
276         ordering = ['date']
277
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)
284
285     def n_patches(self):
286         return self.patches.all().count()
287
288     class Meta:
289         unique_together = [('owner', 'name')]
290
291     def public_url(self):
292         if not self.public:
293             return None
294         site = Site.objects.get_current()
295         return 'http://%s%s' % (site.domain,
296                 reverse('patchwork.views.bundle.public',
297                         kwargs = {
298                                 'username': self.owner.username,
299                                 'bundlename': self.name
300                         }))
301
302     def mbox(self):
303         return '\n'.join([p.mbox().as_string(True) \
304                         for p in self.patches.all()])
305