2012-07-25 16:52:10 +02:00
|
|
|
import json
|
|
|
|
|
2011-04-13 08:50:24 +02:00
|
|
|
from django.db import models
|
2012-05-19 14:11:06 +02:00
|
|
|
from django.core.validators import RegexValidator, validate_email, URLValidator
|
2012-05-28 18:16:48 +02:00
|
|
|
from django.core.exceptions import ValidationError
|
2011-04-13 08:50:24 +02:00
|
|
|
|
2012-06-12 15:27:28 +02:00
|
|
|
from django.contrib.auth.models import User
|
|
|
|
|
2012-05-13 16:42:29 +02:00
|
|
|
from djeuscan.managers import PackageManager, VersionLogManager, \
|
|
|
|
EuscanResultManager
|
2012-05-01 16:56:09 +02:00
|
|
|
|
2012-04-28 18:16:05 +02:00
|
|
|
|
2012-05-28 18:16:48 +02:00
|
|
|
validate_category = RegexValidator("^(?:\w+?-\w+?)|virtual$")
|
2012-05-19 14:11:06 +02:00
|
|
|
validate_name = RegexValidator("^\S+?$")
|
|
|
|
validate_revision = RegexValidator("^r\d+?$")
|
|
|
|
validate_url = URLValidator()
|
|
|
|
|
|
|
|
|
2011-04-13 08:50:24 +02:00
|
|
|
class Herd(models.Model):
|
2012-05-01 16:56:09 +02:00
|
|
|
"""
|
|
|
|
A herd is a collection of packages
|
|
|
|
"""
|
|
|
|
|
2012-05-19 14:11:06 +02:00
|
|
|
herd = models.CharField(max_length=128, unique=True,
|
|
|
|
validators=[validate_name])
|
|
|
|
email = models.CharField(max_length=128, blank=True, null=True,
|
|
|
|
validators=[validate_email])
|
2012-07-20 11:48:34 +02:00
|
|
|
maintainers = models.ManyToManyField("Maintainer")
|
2011-04-13 19:00:31 +02:00
|
|
|
|
|
|
|
def __unicode__(self):
|
|
|
|
if self.email:
|
|
|
|
return '%s <%s>' % (self.herd, self.email)
|
|
|
|
return self.herd
|
2011-04-13 08:50:24 +02:00
|
|
|
|
2012-05-19 14:11:06 +02:00
|
|
|
def save(self, *args, **kwargs):
|
|
|
|
self.full_clean()
|
|
|
|
super(Herd, self).save(*args, **kwargs)
|
|
|
|
|
2012-04-28 18:16:05 +02:00
|
|
|
|
2011-04-13 08:50:24 +02:00
|
|
|
class Maintainer(models.Model):
|
2012-05-01 16:56:09 +02:00
|
|
|
"""
|
|
|
|
The person who maintains a package
|
|
|
|
"""
|
|
|
|
|
2011-04-13 19:00:31 +02:00
|
|
|
name = models.CharField(max_length=128)
|
2012-05-19 14:11:06 +02:00
|
|
|
email = models.CharField(max_length=128, unique=True,
|
|
|
|
validators=[validate_email])
|
2011-04-13 19:00:31 +02:00
|
|
|
|
|
|
|
def __unicode__(self):
|
|
|
|
return '%s <%s>' % (self.name, self.email)
|
|
|
|
|
2012-05-19 14:11:06 +02:00
|
|
|
def save(self, *args, **kwargs):
|
|
|
|
self.full_clean()
|
|
|
|
super(Maintainer, self).save(*args, **kwargs)
|
|
|
|
|
2012-04-28 18:16:05 +02:00
|
|
|
|
2011-04-13 19:00:31 +02:00
|
|
|
class Package(models.Model):
|
2012-05-01 16:56:09 +02:00
|
|
|
"""
|
|
|
|
A portage package
|
|
|
|
"""
|
|
|
|
|
2012-05-19 14:11:06 +02:00
|
|
|
category = models.CharField(max_length=128, validators=[validate_category])
|
|
|
|
name = models.CharField(max_length=128, validators=[validate_name])
|
2011-04-13 19:00:31 +02:00
|
|
|
description = models.TextField(blank=True)
|
2012-05-28 18:16:48 +02:00
|
|
|
homepage = models.TextField(blank=True)
|
2011-04-13 19:00:31 +02:00
|
|
|
herds = models.ManyToManyField(Herd, blank=True)
|
|
|
|
maintainers = models.ManyToManyField(Maintainer, blank=True)
|
|
|
|
|
2012-05-01 16:56:09 +02:00
|
|
|
# For performance, we keep pre-computed counters
|
2011-04-14 08:52:26 +02:00
|
|
|
n_versions = models.IntegerField(default=0)
|
|
|
|
n_packaged = models.IntegerField(default=0)
|
2011-04-25 22:27:32 +02:00
|
|
|
n_overlay = models.IntegerField(default=0)
|
2011-04-14 08:52:26 +02:00
|
|
|
|
2012-05-01 16:56:09 +02:00
|
|
|
# And we also pre-compute last versions
|
2012-04-28 18:16:05 +02:00
|
|
|
last_version_gentoo = models.ForeignKey(
|
|
|
|
'Version', blank=True, null=True, related_name="last_version_gentoo",
|
|
|
|
on_delete=models.SET_NULL
|
|
|
|
)
|
|
|
|
last_version_overlay = models.ForeignKey(
|
|
|
|
'Version', blank=True, null=True, related_name="last_version_overlay",
|
|
|
|
on_delete=models.SET_NULL
|
|
|
|
)
|
|
|
|
last_version_upstream = models.ForeignKey(
|
|
|
|
'Version', blank=True, null=True, related_name="last_version_upstream",
|
|
|
|
on_delete=models.SET_NULL
|
|
|
|
)
|
2012-03-02 18:01:46 +01:00
|
|
|
|
2012-05-01 16:56:09 +02:00
|
|
|
objects = PackageManager()
|
|
|
|
|
2012-05-13 14:09:22 +02:00
|
|
|
class Meta:
|
|
|
|
unique_together = ['category', 'name']
|
2012-05-01 21:15:55 +02:00
|
|
|
|
2012-07-24 07:59:00 +02:00
|
|
|
def cp(self):
|
2011-04-13 19:00:31 +02:00
|
|
|
return '%s/%s' % (self.category, self.name)
|
|
|
|
|
2012-07-24 07:59:00 +02:00
|
|
|
def __unicode__(self):
|
|
|
|
return self.cp()
|
|
|
|
|
2012-05-19 14:11:06 +02:00
|
|
|
def save(self, *args, **kwargs):
|
|
|
|
self.full_clean()
|
2012-05-28 18:16:48 +02:00
|
|
|
|
|
|
|
# Clean urls, accept only real urls
|
|
|
|
urls = []
|
|
|
|
for url in self.homepages:
|
|
|
|
try:
|
|
|
|
validate_url(url)
|
|
|
|
except ValidationError:
|
|
|
|
pass
|
|
|
|
else:
|
|
|
|
urls.append(url)
|
|
|
|
self.homepage = " ".join(urls)
|
|
|
|
|
2012-05-19 14:11:06 +02:00
|
|
|
super(Package, self).save(*args, **kwargs)
|
|
|
|
|
2012-05-13 14:09:22 +02:00
|
|
|
@property
|
|
|
|
def homepages(self):
|
|
|
|
return self.homepage.split(' ')
|
2011-04-13 08:50:24 +02:00
|
|
|
|
2012-09-22 16:08:38 +02:00
|
|
|
@property
|
|
|
|
def last_version(self):
|
|
|
|
from euscan.helpers import vercmp
|
|
|
|
|
|
|
|
versions = [
|
|
|
|
self.last_version_gentoo,
|
|
|
|
self.last_version_overlay,
|
|
|
|
self.last_version_upstream
|
|
|
|
]
|
|
|
|
_cmp = lambda x, y: vercmp(
|
|
|
|
unicode(self), x.version if x else "", y.version if y else ""
|
|
|
|
)
|
|
|
|
return sorted(versions, cmp=_cmp)[-1]
|
|
|
|
|
2012-04-28 18:16:05 +02:00
|
|
|
|
2011-04-13 08:50:24 +02:00
|
|
|
class Version(models.Model):
|
2012-05-01 16:56:09 +02:00
|
|
|
"""
|
|
|
|
Version associated to a package
|
|
|
|
"""
|
|
|
|
|
2011-04-13 19:00:31 +02:00
|
|
|
package = models.ForeignKey(Package)
|
2012-06-08 14:46:37 +02:00
|
|
|
slot = models.CharField(max_length=128, blank=True, default="")
|
2011-04-13 19:00:31 +02:00
|
|
|
revision = models.CharField(max_length=128)
|
|
|
|
version = models.CharField(max_length=128)
|
|
|
|
packaged = models.BooleanField()
|
2012-05-19 14:11:06 +02:00
|
|
|
overlay = models.CharField(max_length=128, default='gentoo', db_index=True,
|
2012-06-08 14:19:20 +02:00
|
|
|
validators=[validate_name], blank=True)
|
2011-04-13 19:00:31 +02:00
|
|
|
urls = models.TextField(blank=True)
|
2011-08-25 15:39:54 +02:00
|
|
|
alive = models.BooleanField(default=True, db_index=True)
|
2011-04-13 19:00:31 +02:00
|
|
|
|
2012-07-17 12:30:12 +02:00
|
|
|
vtype = models.CharField(max_length=128, blank=True)
|
2012-06-26 17:16:02 +02:00
|
|
|
handler = models.CharField(max_length=128, blank=True)
|
|
|
|
confidence = models.IntegerField(default=0)
|
|
|
|
|
2012-07-10 16:15:06 +02:00
|
|
|
ebuild_path = models.CharField(blank=True, max_length=256)
|
|
|
|
metadata_path = models.CharField(blank=True, max_length=256)
|
|
|
|
|
2012-05-13 14:09:22 +02:00
|
|
|
class Meta:
|
|
|
|
unique_together = ['package', 'slot', 'revision', 'version', 'overlay']
|
|
|
|
|
2012-07-24 07:59:00 +02:00
|
|
|
def cpv(self):
|
2012-07-28 11:39:38 +02:00
|
|
|
return '%s/%s-%s%s' % (
|
2012-07-24 07:59:00 +02:00
|
|
|
self.package.category, self.package.name, self.version,
|
2012-08-03 21:58:24 +02:00
|
|
|
'-' + self.revision if self.revision != '-r0' else ''
|
2012-07-24 07:59:00 +02:00
|
|
|
)
|
|
|
|
|
2011-04-13 19:00:31 +02:00
|
|
|
def __unicode__(self):
|
2012-07-28 11:39:38 +02:00
|
|
|
return '%s/%s-%s%s:%s [%s]' % (
|
2012-04-28 18:16:05 +02:00
|
|
|
self.package.category, self.package.name, self.version,
|
2012-08-03 21:58:24 +02:00
|
|
|
'-' + self.revision if self.revision != '-r0' else '',
|
2012-07-24 07:59:00 +02:00
|
|
|
self.slot, self.overlay or "<upstream>"
|
2012-04-28 18:16:05 +02:00
|
|
|
)
|
2011-04-13 19:00:31 +02:00
|
|
|
|
2012-05-19 14:11:06 +02:00
|
|
|
def save(self, *args, **kwargs):
|
|
|
|
self.full_clean()
|
|
|
|
super(Version, self).save(*args, **kwargs)
|
|
|
|
|
2012-04-28 18:16:05 +02:00
|
|
|
|
2011-08-25 15:39:54 +02:00
|
|
|
class VersionLog(models.Model):
|
|
|
|
VERSION_ADDED = 1
|
|
|
|
VERSION_REMOVED = 2
|
|
|
|
VERSION_ACTIONS = (
|
|
|
|
(VERSION_ADDED, 'Added'),
|
|
|
|
(VERSION_REMOVED, 'Removed')
|
|
|
|
)
|
|
|
|
|
|
|
|
package = models.ForeignKey(Package)
|
2012-04-04 14:33:40 +02:00
|
|
|
datetime = models.DateTimeField(auto_now_add=True)
|
2012-06-08 14:46:37 +02:00
|
|
|
slot = models.CharField(max_length=128, blank=True, default="")
|
2011-08-25 15:39:54 +02:00
|
|
|
revision = models.CharField(max_length=128)
|
|
|
|
version = models.CharField(max_length=128)
|
|
|
|
packaged = models.BooleanField()
|
2012-05-19 14:11:06 +02:00
|
|
|
overlay = models.CharField(max_length=128, default='gentoo',
|
2012-06-08 14:19:20 +02:00
|
|
|
validators=[validate_name], blank=True)
|
2011-08-25 15:39:54 +02:00
|
|
|
action = models.IntegerField(choices=VERSION_ACTIONS)
|
|
|
|
|
2012-07-17 13:18:44 +02:00
|
|
|
vtype = models.CharField(max_length=128, blank=True)
|
|
|
|
|
2012-05-05 11:48:12 +02:00
|
|
|
objects = VersionLogManager()
|
|
|
|
|
2011-08-25 15:39:54 +02:00
|
|
|
def __unicode__(self):
|
|
|
|
txt = '+ ' if self.action == self.VERSION_ADDED else '- '
|
2012-04-28 18:16:05 +02:00
|
|
|
txt += '%s/%s-%s-%s:%s [%s]' % (
|
|
|
|
self.package.category, self.package.name, self.version,
|
|
|
|
self.revision, self.slot,
|
2012-07-09 18:15:34 +02:00
|
|
|
self.overlay or '<upstream>'
|
2012-04-28 18:16:05 +02:00
|
|
|
)
|
2011-08-25 15:39:54 +02:00
|
|
|
return txt
|
|
|
|
|
2012-05-19 14:11:06 +02:00
|
|
|
def save(self, *args, **kwargs):
|
|
|
|
self.full_clean()
|
|
|
|
super(VersionLog, self).save(*args, **kwargs)
|
|
|
|
|
2012-05-13 14:09:22 +02:00
|
|
|
def tag(self):
|
|
|
|
return '%s-%s:%s-[%s]' % (self.version, self.revision, self.slot,
|
|
|
|
self.overlay)
|
|
|
|
|
2012-04-28 18:16:05 +02:00
|
|
|
|
2011-04-13 08:50:24 +02:00
|
|
|
class EuscanResult(models.Model):
|
2011-04-13 19:00:31 +02:00
|
|
|
package = models.ForeignKey(Package)
|
2011-04-14 08:52:26 +02:00
|
|
|
datetime = models.DateTimeField()
|
2011-04-13 19:00:31 +02:00
|
|
|
result = models.TextField(blank=True)
|
2011-04-14 08:52:26 +02:00
|
|
|
|
2012-06-26 18:13:28 +02:00
|
|
|
scan_time = models.FloatField(null=True, blank=True)
|
|
|
|
ebuild = models.CharField(blank=True, max_length=256)
|
|
|
|
|
2012-05-13 16:42:29 +02:00
|
|
|
objects = EuscanResultManager()
|
|
|
|
|
2012-05-01 21:15:55 +02:00
|
|
|
class Meta:
|
|
|
|
get_latest_by = "datetime"
|
|
|
|
|
2012-05-19 14:11:06 +02:00
|
|
|
def save(self, *args, **kwargs):
|
|
|
|
self.full_clean()
|
|
|
|
super(EuscanResult, self).save(*args, **kwargs)
|
|
|
|
|
2012-07-27 12:55:57 +02:00
|
|
|
@property
|
2012-07-25 16:52:10 +02:00
|
|
|
def messages(self):
|
2012-07-28 11:39:38 +02:00
|
|
|
try:
|
|
|
|
result = json.loads(self.result)
|
|
|
|
except ValueError:
|
|
|
|
return self.result
|
2012-07-25 16:52:10 +02:00
|
|
|
|
|
|
|
if result and self.package.cp() in result:
|
|
|
|
return result[self.package.cp()]['messages']
|
|
|
|
else:
|
|
|
|
return ""
|
|
|
|
|
2012-06-03 09:43:49 +02:00
|
|
|
def __unicode__(self):
|
|
|
|
return '[%s] %s/%s' % (
|
|
|
|
self.datetime, self.package.category, self.package.name
|
|
|
|
)
|
|
|
|
|
2012-04-28 18:16:05 +02:00
|
|
|
|
2012-07-26 14:37:03 +02:00
|
|
|
class Category(models.Model):
|
|
|
|
name = models.CharField(max_length=128, validators=[validate_category],
|
|
|
|
unique=True)
|
|
|
|
|
|
|
|
def __unicode__(self):
|
|
|
|
return self.name
|
|
|
|
|
|
|
|
|
|
|
|
class Overlay(models.Model):
|
|
|
|
name = models.CharField(max_length=128, validators=[validate_name],
|
|
|
|
unique=True)
|
|
|
|
|
|
|
|
def __unicode__(self):
|
|
|
|
return self.name
|
|
|
|
|
|
|
|
|
|
|
|
class UserProfile(models.Model):
|
2012-08-11 15:09:42 +02:00
|
|
|
EMAIL_SCAN = 1
|
|
|
|
EMAIL_WEEKLY = 2
|
|
|
|
EMAIL_MONTHLY = 3
|
|
|
|
EMAIL_OPTS = (
|
|
|
|
(EMAIL_SCAN, 'On updates'),
|
|
|
|
(EMAIL_WEEKLY, 'Weekly'),
|
|
|
|
(EMAIL_MONTHLY, 'Monthly')
|
|
|
|
)
|
|
|
|
|
2012-07-26 14:37:03 +02:00
|
|
|
user = models.OneToOneField(User)
|
|
|
|
herds = models.ManyToManyField(Herd)
|
|
|
|
maintainers = models.ManyToManyField(Maintainer)
|
|
|
|
packages = models.ManyToManyField(Package)
|
|
|
|
categories = models.ManyToManyField(Category)
|
|
|
|
overlays = models.ManyToManyField(Overlay)
|
|
|
|
|
2012-08-11 13:31:57 +02:00
|
|
|
feed_upstream_info = models.BooleanField(default=True)
|
|
|
|
feed_portage_info = models.BooleanField(default=False)
|
|
|
|
feed_show_adds = models.BooleanField(default=True)
|
|
|
|
feed_show_removals = models.BooleanField(default=True)
|
|
|
|
feed_ignore_pre = models.BooleanField(default=False)
|
|
|
|
feed_ignore_pre_if_stable = models.BooleanField(default=False)
|
|
|
|
|
|
|
|
email_activated = models.BooleanField(default=True)
|
2012-08-11 15:09:42 +02:00
|
|
|
email_every = models.IntegerField(choices=EMAIL_OPTS, default=EMAIL_SCAN)
|
2012-08-11 13:31:57 +02:00
|
|
|
email_ignore_pre = models.BooleanField(default=False)
|
|
|
|
email_ignore_pre_if_stable = models.BooleanField(default=False)
|
2012-08-11 15:09:42 +02:00
|
|
|
last_email = models.DateTimeField(auto_now_add=True)
|
2012-08-08 14:33:37 +02:00
|
|
|
|
2012-07-26 14:37:03 +02:00
|
|
|
|
2011-05-05 18:10:47 +02:00
|
|
|
class Log(models.Model):
|
2012-05-01 16:56:09 +02:00
|
|
|
"""
|
|
|
|
Model used for keeping data for charts
|
|
|
|
"""
|
|
|
|
|
2011-04-14 08:52:26 +02:00
|
|
|
datetime = models.DateTimeField()
|
|
|
|
|
2012-05-01 16:56:09 +02:00
|
|
|
# Packages up to date in the main portage tree
|
2012-04-28 18:16:05 +02:00
|
|
|
n_packages_gentoo = models.IntegerField(default=0)
|
2012-05-01 16:56:09 +02:00
|
|
|
|
|
|
|
# Packages up to date in an overlay
|
2012-04-28 18:16:05 +02:00
|
|
|
n_packages_overlay = models.IntegerField(default=0)
|
2012-05-01 16:56:09 +02:00
|
|
|
|
|
|
|
# Packages outdated
|
2011-08-25 15:39:54 +02:00
|
|
|
n_packages_outdated = models.IntegerField(default=0)
|
|
|
|
|
2012-05-01 16:56:09 +02:00
|
|
|
# Versions in the main portage tree
|
2012-04-28 18:16:05 +02:00
|
|
|
n_versions_gentoo = models.IntegerField(default=0)
|
2012-05-01 16:56:09 +02:00
|
|
|
|
|
|
|
# Versions in overlays
|
2012-04-28 18:16:05 +02:00
|
|
|
n_versions_overlay = models.IntegerField(default=0)
|
2012-05-01 16:56:09 +02:00
|
|
|
|
|
|
|
# Upstream versions, not in the main tree or overlays
|
2011-08-25 15:39:54 +02:00
|
|
|
n_versions_upstream = models.IntegerField(default=0)
|
2011-05-03 08:19:01 +02:00
|
|
|
|
|
|
|
def __unicode__(self):
|
2012-04-28 18:16:05 +02:00
|
|
|
return u'[%d:%d:%d] [%d:%d:%d]' % (
|
|
|
|
self.n_packages_gentoo, self.n_packages_overlay,
|
|
|
|
self.n_packages_outdated, self.n_versions_gentoo,
|
|
|
|
self.n_versions_overlay, self.n_versions_upstream
|
|
|
|
)
|
|
|
|
|
2012-05-19 14:11:06 +02:00
|
|
|
def save(self, *args, **kwargs):
|
|
|
|
self.full_clean()
|
|
|
|
super(Log, self).save(*args, **kwargs)
|
|
|
|
|
2011-05-03 08:19:01 +02:00
|
|
|
|
2011-05-05 18:10:47 +02:00
|
|
|
class WorldLog(Log):
|
|
|
|
def __unicode__(self):
|
|
|
|
return u'world ' + Log.__unicode__(self)
|
2011-04-14 08:52:26 +02:00
|
|
|
|
2012-04-28 18:16:05 +02:00
|
|
|
|
2011-05-05 18:10:47 +02:00
|
|
|
class CategoryLog(Log):
|
2012-05-19 14:11:06 +02:00
|
|
|
category = models.CharField(max_length=128, validators=[validate_category])
|
2011-04-14 08:52:26 +02:00
|
|
|
|
2011-05-05 18:10:47 +02:00
|
|
|
def __unicode__(self):
|
|
|
|
return u'%s %s' % (self.category, Log.__unicode__(self))
|
2011-05-03 08:19:01 +02:00
|
|
|
|
2012-04-28 18:16:05 +02:00
|
|
|
|
2011-05-05 18:10:47 +02:00
|
|
|
class HerdLog(Log):
|
|
|
|
herd = models.ForeignKey(Herd)
|
2011-05-03 08:19:01 +02:00
|
|
|
|
|
|
|
def __unicode__(self):
|
2011-05-05 18:10:47 +02:00
|
|
|
return u'%s %s' % (self.herd, Log.__unicode__(self))
|
2011-04-14 08:52:26 +02:00
|
|
|
|
2012-04-28 18:16:05 +02:00
|
|
|
|
2011-05-05 18:10:47 +02:00
|
|
|
class MaintainerLog(Log):
|
2011-04-14 08:52:26 +02:00
|
|
|
maintainer = models.ForeignKey(Maintainer)
|
2011-05-03 08:19:01 +02:00
|
|
|
|
|
|
|
def __unicode__(self):
|
2011-05-05 18:10:47 +02:00
|
|
|
return u'%s %s' % (self.maintainer, Log.__unicode__(self))
|
2012-06-08 14:19:20 +02:00
|
|
|
|
|
|
|
|
|
|
|
class RefreshPackageQuery(models.Model):
|
2012-07-07 15:44:00 +02:00
|
|
|
package = models.ForeignKey(Package)
|
2012-06-08 14:19:20 +02:00
|
|
|
priority = models.IntegerField(default=0)
|
2012-07-20 17:10:12 +02:00
|
|
|
users = models.ManyToManyField(User)
|
2012-06-08 14:19:20 +02:00
|
|
|
|
2012-07-27 12:21:48 +02:00
|
|
|
@property
|
|
|
|
def position(self):
|
|
|
|
ordered = RefreshPackageQuery.objects.all().order_by("-priority")
|
|
|
|
for pos, obj in enumerate(ordered, start=1):
|
|
|
|
if obj == self:
|
|
|
|
return pos
|
|
|
|
|
2012-06-08 14:19:20 +02:00
|
|
|
def __unicode__(self):
|
2012-07-07 15:44:00 +02:00
|
|
|
return u'[%d] %s' % (self.priority, self.package)
|
2012-06-12 15:27:28 +02:00
|
|
|
|
|
|
|
|
2012-07-09 14:29:42 +02:00
|
|
|
class ProblemReport(models.Model):
|
|
|
|
package = models.ForeignKey(Package)
|
|
|
|
version = models.ForeignKey(Version, null=True, blank=True)
|
|
|
|
subject = models.CharField(max_length=128)
|
|
|
|
message = models.TextField()
|
|
|
|
datetime = models.DateTimeField(auto_now_add=True)
|
|
|
|
|
|
|
|
def __unicode__(self):
|
|
|
|
return u"[%s] %s" % (self.datetime, self.package)
|