diff options
| author | Luke Shumaker <LukeShu@sbcglobal.net> | 2013-04-21 02:22:44 -0400 | 
|---|---|---|
| committer | Luke Shumaker <LukeShu@sbcglobal.net> | 2013-04-21 02:22:44 -0400 | 
| commit | 03fa7e4f27bdb39a8f8f5ed91a87d18bf8357b47 (patch) | |
| tree | c67eafcbda55706f18400b3115a2b8a5be318394 /packages/models.py | |
| parent | 91c451821ce7000cbc268cec8427d208a6cedd7e (diff) | |
| parent | b8ee7b1ee281b45b245fb454228b8ad847c56200 (diff) | |
Merge branch 'archweb' into archweb-generic2
Conflicts:
	devel/views.py
	feeds.py
	public/views.py
	settings.py
	sitestatic/archweb.js
	templates/base.html
	templates/devel/profile.html
	templates/mirrors/status.html
	templates/news/view.html
	templates/packages/flaghelp.html
	templates/packages/opensearch.xml
	templates/public/download.html
	templates/public/feeds.html
	templates/public/index.html
	templates/registration/login.html
	templates/releng/results.html
	templates/todolists/public_list.html
Diffstat (limited to 'packages/models.py')
| -rw-r--r-- | packages/models.py | 301 | 
1 files changed, 261 insertions, 40 deletions
| diff --git a/packages/models.py b/packages/models.py index 820e61ba..7bcdc000 100644 --- a/packages/models.py +++ b/packages/models.py @@ -2,10 +2,13 @@ from collections import namedtuple  from django.db import models  from django.db.models.signals import pre_save +from django.contrib.admin.models import ADDITION, CHANGE, DELETION  from django.contrib.auth.models import User -from main.models import Arch, Repo -from main.utils import set_created_field +from main.models import Arch, Repo, Package +from main.utils import set_created_field, database_vendor +from packages.alpm import AlpmAPI +  class PackageRelation(models.Model):      ''' @@ -26,13 +29,11 @@ class PackageRelation(models.Model):      created = models.DateTimeField(editable=False)      def get_associated_packages(self): -        # TODO: delayed import to avoid circular reference -        from main.models import Package          return Package.objects.normal().filter(pkgbase=self.pkgbase)      def repositories(self):          packages = self.get_associated_packages() -        return sorted(set([p.repo for p in packages])) +        return sorted({p.repo for p in packages})      def __unicode__(self):          return u'%s: %s (%s)' % ( @@ -138,7 +139,7 @@ class Signoff(models.Model):      arch = models.ForeignKey(Arch)      repo = models.ForeignKey(Repo)      user = models.ForeignKey(User, related_name="package_signoffs") -    created = models.DateTimeField(editable=False) +    created = models.DateTimeField(editable=False, db_index=True)      revoked = models.DateTimeField(null=True)      comments = models.TextField(null=True, blank=True) @@ -146,8 +147,6 @@ class Signoff(models.Model):      @property      def packages(self): -        # TODO: delayed import to avoid circular reference -        from main.models import Package          return Package.objects.normal().filter(pkgbase=self.pkgbase,                  pkgver=self.pkgver, pkgrel=self.pkgrel, epoch=self.epoch,                  arch=self.arch, repo=self.repo) @@ -172,10 +171,14 @@ class FlagRequest(models.Model):      '''      user = models.ForeignKey(User, blank=True, null=True)      user_email = models.EmailField('email address') -    created = models.DateTimeField(editable=False) -    ip_address = models.IPAddressField('IP address') +    created = models.DateTimeField(editable=False, db_index=True) +    # Great work, Django... https://code.djangoproject.com/ticket/18212 +    ip_address = models.GenericIPAddressField(verbose_name='IP address', +            unpack_ipv4=True)      pkgbase = models.CharField(max_length=255, db_index=True) -    version = models.CharField(max_length=255, default='') +    pkgver = models.CharField(max_length=255) +    pkgrel = models.CharField(max_length=255) +    epoch = models.PositiveIntegerField(default=0)      repo = models.ForeignKey(Repo)      num_packages = models.PositiveIntegerField('number of packages', default=1)      message = models.TextField('message to developer', blank=True) @@ -192,76 +195,294 @@ class FlagRequest(models.Model):              return self.user.get_full_name()          return self.user_email +    @property +    def full_version(self): +        # Difference here from other implementations at the moment: we need to +        # handle the case of pkgver and pkgrel being null as this table didn't +        # originally have version columns. +        if self.pkgver == '' and self.pkgrel == '': +            return u'' +        if self.epoch > 0: +            return u'%d:%s-%s' % (self.epoch, self.pkgver, self.pkgrel) +        return u'%s-%s' % (self.pkgver, self.pkgrel) + +    def get_associated_packages(self): +        return Package.objects.normal().filter( +                pkgbase=self.pkgbase, +                repo__testing=self.repo.testing, +                repo__staging=self.repo.staging).order_by( +                'pkgname', 'repo__name', 'arch__name') +      def __unicode__(self):          return u'%s from %s on %s' % (self.pkgbase, self.who(), self.created) + +class UpdateManager(models.Manager): +    def log_update(self, old_pkg, new_pkg): +        '''Utility method to help log an update. This will determine the type +        based on how many packages are passed in, and will pull the relevant +        necesary fields off the given packages. +        Note that in some cases, this is a no-op if we know this database type +        supports triggers to add these rows instead.''' +        if database_vendor(Package, 'write') in ('sqlite', 'postgresql'): +            # we log updates using database triggers for these backends +            return +        update = Update() +        if new_pkg: +            update.action_flag = ADDITION +            update.package = new_pkg +            update.arch = new_pkg.arch +            update.repo = new_pkg.repo +            update.pkgname = new_pkg.pkgname +            update.pkgbase = new_pkg.pkgbase +            update.new_pkgver = new_pkg.pkgver +            update.new_pkgrel = new_pkg.pkgrel +            update.new_epoch = new_pkg.epoch +        if old_pkg: +            if new_pkg: +                update.action_flag = CHANGE +                # ensure we should even be logging this +                if (old_pkg.pkgver == new_pkg.pkgver and +                        old_pkg.pkgrel == new_pkg.pkgrel and +                        old_pkg.epoch == new_pkg.epoch): +                    # all relevant fields were the same; e.g. a force update +                    return +            else: +                update.action_flag = DELETION +                update.arch = old_pkg.arch +                update.repo = old_pkg.repo +                update.pkgname = old_pkg.pkgname +                update.pkgbase = old_pkg.pkgbase + +            update.old_pkgver = old_pkg.pkgver +            update.old_pkgrel = old_pkg.pkgrel +            update.old_epoch = old_pkg.epoch + +        update.save(force_insert=True) +        return update + + +class Update(models.Model): +    UPDATE_ACTION_CHOICES = ( +        (ADDITION, 'Addition'), +        (CHANGE, 'Change'), +        (DELETION, 'Deletion'), +    ) + +    package = models.ForeignKey(Package, related_name="updates", +            null=True, on_delete=models.SET_NULL) +    repo = models.ForeignKey(Repo, related_name="updates") +    arch = models.ForeignKey(Arch, related_name="updates") +    pkgname = models.CharField(max_length=255, db_index=True) +    pkgbase = models.CharField(max_length=255) +    action_flag = models.PositiveSmallIntegerField('action flag', +            choices=UPDATE_ACTION_CHOICES) +    created = models.DateTimeField(editable=False, db_index=True) + +    old_pkgver = models.CharField(max_length=255, null=True) +    old_pkgrel = models.CharField(max_length=255, null=True) +    old_epoch = models.PositiveIntegerField(null=True) + +    new_pkgver = models.CharField(max_length=255, null=True) +    new_pkgrel = models.CharField(max_length=255, null=True) +    new_epoch = models.PositiveIntegerField(null=True) + +    objects = UpdateManager() + +    class Meta: +        get_latest_by = 'created' + +    def is_addition(self): +        return self.action_flag == ADDITION + +    def is_change(self): +        return self.action_flag == CHANGE + +    def is_deletion(self): +        return self.action_flag == DELETION + +    @property +    def old_version(self): +        if self.action_flag == ADDITION: +            return None +        if self.old_epoch > 0: +            return u'%d:%s-%s' % (self.old_epoch, self.old_pkgver, self.old_pkgrel) +        return u'%s-%s' % (self.old_pkgver, self.old_pkgrel) + +    @property +    def new_version(self): +        if self.action_flag == DELETION: +            return None +        if self.new_epoch > 0: +            return u'%d:%s-%s' % (self.new_epoch, self.new_pkgver, self.new_pkgrel) +        return u'%s-%s' % (self.new_pkgver, self.new_pkgrel) + +    def elsewhere(self): +        return Package.objects.normal().filter( +                pkgname=self.pkgname, arch=self.arch) + +    def __unicode__(self): +        return u'%s of %s on %s' % (self.get_action_flag_display(), +                self.pkgname, self.created) + +  class PackageGroup(models.Model):      '''      Represents a group a package is in. There is no actual group entity,      only names that link to given packages.      ''' -    pkg = models.ForeignKey('main.Package', related_name='groups') +    pkg = models.ForeignKey(Package, related_name='groups')      name = models.CharField(max_length=255, db_index=True)      def __unicode__(self):          return "%s: %s" % (self.name, self.pkg) +    class Meta: +        ordering = ('name',) + +  class License(models.Model): -    pkg = models.ForeignKey('main.Package', related_name='licenses') +    pkg = models.ForeignKey(Package, related_name='licenses')      name = models.CharField(max_length=255)      def __unicode__(self):          return self.name      class Meta: -        ordering = ['name'] +        ordering = ('name',) -class Conflict(models.Model): -    pkg = models.ForeignKey('main.Package', related_name='conflicts') + +class RelatedToBase(models.Model): +    '''A base class for conflicts/provides/replaces/etc.'''      name = models.CharField(max_length=255, db_index=True) -    comparison = models.CharField(max_length=255, default='')      version = models.CharField(max_length=255, default='') +    def get_best_satisfier(self): +        '''Find a satisfier for this related package that best matches the +        given criteria. It will not search provisions, but will find packages +        named and matching repo characteristics if possible.''' +        pkgs = Package.objects.normal().filter(pkgname=self.name) +        if not self.pkg.arch.agnostic: +            # make sure we match architectures if possible +            arches = self.pkg.applicable_arches() +            pkgs = pkgs.filter(arch__in=arches) +        # if we have a comparison operation, make sure the packages we grab +        # actually satisfy the requirements +        if self.comparison and self.version: +            alpm = AlpmAPI() +            pkgs = [pkg for pkg in pkgs if not alpm.available or +                    alpm.compare_versions(pkg.full_version, self.comparison, +                        self.version)] +        if len(pkgs) == 0: +            # couldn't find a package in the DB +            # it should be a virtual depend (or a removed package) +            return None +        if len(pkgs) == 1: +            return pkgs[0] +        # more than one package, see if we can't shrink it down +        # grab the first though in case we fail +        pkg = pkgs[0] +        # prevents yet more DB queries, these lists should be short; +        # after each grab the best available in case we remove all entries +        pkgs = [p for p in pkgs if p.repo.staging == self.pkg.repo.staging] +        if len(pkgs) > 0: +            pkg = pkgs[0] + +        pkgs = [p for p in pkgs if p.repo.testing == self.pkg.repo.testing] +        if len(pkgs) > 0: +            pkg = pkgs[0] + +        return pkg + +    def get_providers(self): +        '''Return providers of this related package. Does *not* include exact +        matches as it checks the Provision names only, use get_best_satisfier() +        instead for exact matches.''' +        pkgs = Package.objects.normal().filter( +                provides__name=self.name).order_by().distinct() +        if not self.pkg.arch.agnostic: +            # make sure we match architectures if possible +            arches = self.pkg.applicable_arches() +            pkgs = pkgs.filter(arch__in=arches) + +        # If we have a comparison operation, make sure the packages we grab +        # actually satisfy the requirements. +        alpm = AlpmAPI() +        if alpm.available and self.comparison and self.version: +            pkgs = pkgs.prefetch_related('provides') +            new_pkgs = [] +            for package in pkgs: +                for provide in package.provides.all(): +                    if provide.name != self.name: +                        continue +                    if alpm.compare_versions(provide.version, +                            self.comparison, self.version): +                        new_pkgs.append(package) +            pkgs = new_pkgs + +        # Sort providers by preference. We sort those in same staging/testing +        # combination first, followed by others. We sort by a (staging, +        # testing) match tuple that will be (True, True) in the best case. +        key_func = lambda x: (x.repo.staging == self.pkg.repo.staging, +                x.repo.testing == self.pkg.repo.testing) +        return sorted(pkgs, key=key_func, reverse=True) +      def __unicode__(self):          if self.version:              return u'%s%s%s' % (self.name, self.comparison, self.version)          return self.name      class Meta: -        ordering = ['name'] +        abstract = True +        ordering = ('name',) -class Provision(models.Model): -    pkg = models.ForeignKey('main.Package', related_name='provides') -    name = models.CharField(max_length=255, db_index=True) -    # comparison must be '=' for provides -    comparison = '=' -    version = models.CharField(max_length=255, default='') + +class Depend(RelatedToBase): +    DEPTYPE_CHOICES = ( +        ('D', 'Depend'), +        ('O', 'Optional Depend'), +        ('M', 'Make Depend'), +        ('C', 'Check Depend'), +    ) + +    pkg = models.ForeignKey(Package, related_name='depends') +    comparison = models.CharField(max_length=255, default='') +    description = models.TextField(null=True, blank=True) +    deptype = models.CharField(max_length=1, default='D', +            choices=DEPTYPE_CHOICES)      def __unicode__(self): -        if self.version: -            return u'%s=%s' % (self.name, self.version) -        return self.name +        '''For depends, we may also have a description and a modifier.''' +        to_str = super(Depend, self).__unicode__() +        if self.description: +            return u'%s: %s' % (to_str, self.description) +        return to_str -    class Meta: -        ordering = ['name'] -class Replacement(models.Model): -    pkg = models.ForeignKey('main.Package', related_name='replaces') -    name = models.CharField(max_length=255, db_index=True) +class Conflict(RelatedToBase): +    pkg = models.ForeignKey(Package, related_name='conflicts')      comparison = models.CharField(max_length=255, default='') -    version = models.CharField(max_length=255, default='') -    def __unicode__(self): -        if self.version: -            return u'%s%s%s' % (self.name, self.comparison, self.version) -        return self.name -    class Meta: -        ordering = ['name'] +class Provision(RelatedToBase): +    pkg = models.ForeignKey(Package, related_name='provides') +    # comparison must be '=' for provides + +    @property +    def comparison(self): +        if self.version is not None and self.version != '': +            return '=' +        return None + + +class Replacement(RelatedToBase): +    pkg = models.ForeignKey(Package, related_name='replaces') +    comparison = models.CharField(max_length=255, default='')  # hook up some signals -for sender in (PackageRelation, SignoffSpecification, Signoff): +for sender in (FlagRequest, PackageRelation, +        SignoffSpecification, Signoff, Update):      pre_save.connect(set_created_field, sender=sender,              dispatch_uid="packages.models") | 
