Implemented saving the tarball of a package
[aur2.git] / archlinux / aur / models.py
blob571b2c903f9606ffa119cbc613b215cd3d036a7f
1 from django.db import models
2 from django.contrib.admin.models import User
3 from django import newforms as forms # This will change to forms in 0.98 or 1.0
4 from django.core.mail import send_mass_mail
5 from django.db.models import signals, permalink
6 from django.dispatch import dispatcher
8 from django.template.loader import render_to_string
10 from datetime import datetime
11 import os
13 class Category(models.Model):
14 name = models.CharField(max_length=20)
16 def __unicode__(self):
17 return self.name
19 class Admin:
20 pass
22 class Meta:
23 verbose_name_plural = 'categories'
26 class Architecture(models.Model):
27 name = models.CharField(max_length=10)
29 def __unicode__(self):
30 return self.name
32 class Admin:
33 pass
36 class Repository(models.Model):
37 name = models.CharField(max_length=20)
39 def __unicode__(self):
40 return self.name
42 class Admin:
43 pass
45 class Meta:
46 verbose_name_plural = 'repositories'
49 class License(models.Model):
50 name = models.CharField(max_length=24)
52 def __unicode__(self):
53 return self.name
55 class Admin:
56 pass
59 class Group(models.Model):
60 name = models.CharField(max_length=10)
62 def __unicode__(self):
63 return self.name
65 class Admin:
66 pass
69 class Package(models.Model):
70 name = models.CharField(primary_key=True, max_length=30)
71 version = models.CharField(max_length=20)
72 release = models.SmallIntegerField()
73 description = models.CharField(max_length=180)
74 url = models.CharField(max_length=200, null=True, blank=True)
75 maintainers = models.ManyToManyField(User)
76 repository = models.ForeignKey(Repository)
77 category = models.ForeignKey(Category)
78 tarball = models.FileField(upload_to='packages')
79 licenses = models.ManyToManyField(License, null=True, blank=True)
80 architectures = models.ManyToManyField(Architecture)
81 depends = models.ManyToManyField('self', null=True, blank=True,
82 related_name="reverse_depends", symmetrical=False)
83 make_depends = models.ManyToManyField('self', null=True, blank=True,
84 related_name="reverse_make_depends", symmetrical=False)
85 provides = models.ManyToManyField('self', null=True, blank=True,
86 related_name="reverse_provides", symmetrical=False)
87 replaces = models.ManyToManyField('self', null=True, blank=True,
88 related_name="reverse_replaces", symmetrical=False)
89 conflicts = models.ManyToManyField('self', null=True, blank=True,
90 related_name="reverse_conflicts", symmetrical=False)
91 deleted = models.BooleanField(default=False)
92 outdated = models.BooleanField(default=False)
93 added = models.DateTimeField(editable=False, default=datetime.now())
94 updated = models.DateTimeField()
95 groups = models.ManyToManyField(Group, null=True, blank=True)
97 def __unicode__(self):
98 return "%s %s" % (self.name, self.version)
100 def get_arch(self):
101 return ', '.join(map(str, self.architectures.all()))
102 get_arch.short_description = 'architectures'
104 def get_tarball_basename(self):
105 """Return the basename of the absolute path to the tarball"""
106 return os.path.basename(self.get_tarball_filename())
108 def get_absolute_url(self):
109 return ('aur-package_detail', [self.name,])
110 get_absolute_url = permalink(get_absolute_url)
112 def save(self):
113 self.updated = datetime.now()
114 super(Package, self).save()
116 def _save_FIELD_file(self, field, filename, raw_contents, save=True):
117 old_upload_to=field.upload_to
118 dirname, filename = filename.rsplit(os.path.sep, 1)
119 field.upload_to = os.path.join(field.upload_to, dirname)
120 super(Package, self)._save_FIELD_file(field, filename,
121 raw_contents, save)
122 field.upload_to = old_upload_to
124 class Admin:
125 list_display = ('name', 'category', 'get_arch', 'updated')
127 class Meta:
128 ordering = ('-updated',)
129 get_latest_by = 'updated'
132 class PackageFile(models.Model):
133 package = models.ForeignKey(Package)
134 # filename for local sources and url for external
135 filename = models.FileField(upload_to='packages', null=True, blank=True)
136 url = models.URLField(null=True, blank=True)
138 def get_absolute_url(self):
139 if self.filename:
140 return self.get_filename_url()
141 else:
142 return self.url
144 def get_filename(self):
145 if self.filename:
146 return os.path.basename(self.get_filename_filename())
147 else:
148 return self.url
150 def _save_FIELD_file(self, field, filename, raw_contents, save=True):
151 old_upload_to=field.upload_to
152 dirname, filename = filename.rsplit(os.path.sep, 1)
153 field.upload_to = os.path.join(field.upload_to, dirname)
154 super(PackageFile, self)._save_FIELD_file(field, filename,
155 raw_contents, save)
156 field.upload_to = old_upload_to
158 def __unicode__(self):
159 return self.filename
161 class Admin:
162 pass
164 class PackageHash(models.Model):
165 # sha512 hashes are 128 characters
166 hash = models.CharField(max_length=128, primary_key=True)
167 type = models.CharField(max_length=12)
168 file = models.ForeignKey(PackageFile)
170 def __unicode__(self):
171 return self.hash
173 class Meta:
174 verbose_name_plural = 'package hashes'
176 class Admin:
177 pass
180 class Comment(models.Model):
181 package = models.ForeignKey(Package)
182 parent = models.ForeignKey('self', null=True, blank=True)
183 user = models.ForeignKey(User)
184 message = models.TextField()
185 added = models.DateTimeField(editable=False, default=datetime.now())
186 ip = models.IPAddressField()
187 hidden = models.BooleanField(default=False)
188 commit = models.BooleanField(default=False)
190 def __unicode__(self):
191 return self.message
193 class Admin:
194 pass
197 class PackageNotification(models.Model):
198 user = models.ForeignKey(User)
199 package = models.ForeignKey(Package)
201 def __unicode__(self):
202 return "%s subscription to %s updates" % (self.user.username, self.package.name)
204 class Admin:
205 pass
207 class PackageSearchForm(forms.Form):
208 # Borrowed from AUR2-BR
209 def __init__(self, *args, **kwargs):
210 super(PackageSearchForm, self).__init__(*args, **kwargs)
211 category_choices = [('all', 'All')]
212 category_choices += [(category.name.lower(), category.name) for category in Category.objects.all()]
213 repository_choices = [('all', 'All')]
214 repository_choices += [(repository.name.lower(), repository.name) for repository in Repository.objects.all()]
215 self.fields['category'].choices = category_choices
216 self.fields['repository'].choices = repository_choices
218 repository = forms.ChoiceField(initial='all', choices=())
219 category = forms.ChoiceField(initial='all', choices=())
220 query = forms.CharField(max_length=30, label="Keywords", required=False)
221 searchby = forms.ChoiceField(initial='name', label="Search By",choices=(
222 ('name', 'Package Name'),
223 ('maintainer', 'Maintainer'),
225 lastupdate = forms.DateTimeField(label="Last Update", required=False)
226 sortby = forms.ChoiceField(initial='name', label="Sort By", choices=(
227 ('name', 'Package Name'),
228 ('category', 'Category'),
229 ('repository', 'Repository'),
230 ('updated', 'Last Updated'),
232 order = forms.ChoiceField(initial='asc', choices=(
233 ('asc', 'Ascending'),
234 ('desc', 'Descending'),
236 limit = forms.ChoiceField(initial='25', choices=(
237 (25, 25),
238 (50, 50),
239 (75, 75),
240 (100, 100),
241 (150, 150),
244 def get_or_default(self, key):
245 if not self.is_bound:
246 return self.fields[key].initial
247 return self.cleaned_data.get(key, self.fields[key].initial)
249 def search(self):
250 if self.is_bound and not self.is_valid():
251 return None
252 repository = self.get_or_default('repository')
253 lastupdate = self.get_or_default('lastupdate')
254 category = self.get_or_default('category')
255 sortby = self.get_or_default('sortby')
256 order = self.get_or_default('order')
258 # Find the packages by searching description and package name or maintainer
259 if self.get_or_default('query'):
260 if self.get_or_default('searchby') == 'maintainer':
261 results = Package.objects.filter(maintainers__username__icontains=self.cleaned_data["query"])
262 else:
263 res1 = Package.objects.filter(name__icontains=self.cleaned_data["query"])
264 res2 = Package.objects.filter(description__icontains=self.cleaned_data["query"])
265 results = res1 | res2
266 else:
267 results = Package.objects.all()
268 # Restrict results
269 if repository != 'all':
270 results = results.filter(repository__name__iexact=repository)
271 if category != 'all':
272 results = results.filter(category__name__exact=category)
273 if lastupdate:
274 results = results.filter(updated__gte=lastupdate)
275 # Change the sort order if necessary
276 if order == 'desc':
277 results = results.order_by('-' + sortby, 'repository', 'category', 'name')
278 else:
279 results = results.order_by(sortby, 'repository', 'category', 'name')
280 return results
282 class PackageSubmitForm(forms.Form):
283 # Borrowed from AUR2-BR
284 def __init__(self, *args, **kwargs):
285 super(PackageSubmitForm, self).__init__(*args, **kwargs)
286 category_choices = [(category.name.lower(), category.name) for category in Category.objects.all()]
287 self.fields['category'].choices = category_choices
289 category = forms.ChoiceField(choices=())
290 file = forms.FileField(label="PKGBUILD")
291 comment = forms.CharField(widget=forms.Textarea, label="Commit Message")
293 # Should this be here?
294 def email_package_updates(sender, instance, signal, *args, **kwargs):
295 """Send notification to users of modification to a Package"""
296 subject = "Archlinux AUR: %s updated" % instance.name
297 sender = 'xilon'
298 mail_list = []
299 notifications = PackageNotification.objects.filter(package=instance)
300 for notification in notifications:
301 message = render_to_string('aur/email_notification.txt', {
302 'package': instance,
303 'user': notification.user,
305 mail_list.append((subject, message, sender,
306 (notification.user.email,)))
307 return send_mass_mail(mail_list)
309 # Send notifications of updates to users on saves and deltion of packages
310 dispatcher.connect(email_package_updates, signal=signals.post_save,
311 sender=Package)
312 dispatcher.connect(email_package_updates, signal=signals.post_delete,
313 sender=Package)