Esempio n. 1
0
class Tourtment(SQLObject):
    class sqlmeta:
        table = 'competition'  # test on a non-standard way

    name = StringCol()
    fightersAsList = RelatedJoin('Fighter')
    fightersAsSResult = SQLRelatedJoin('Fighter')
Esempio n. 2
0
class Cve(SQLBase, BugLinkTargetMixin):
    """A CVE database record."""

    implements(ICve, IBugLinkTarget)

    _table = 'Cve'

    sequence = StringCol(notNull=True, alternateID=True)
    status = EnumCol(dbName='status', schema=CveStatus, notNull=True)
    description = StringCol(notNull=True)
    datecreated = UtcDateTimeCol(notNull=True, default=UTC_NOW)
    datemodified = UtcDateTimeCol(notNull=True, default=UTC_NOW)

    # joins
    bugs = SQLRelatedJoin('Bug', intermediateTable='BugCve',
        joinColumn='cve', otherColumn='bug', orderBy='id')
    bug_links = SQLMultipleJoin('BugCve', joinColumn='cve', orderBy='id')
    references = SQLMultipleJoin(
        'CveReference', joinColumn='cve', orderBy='id')

    @property
    def url(self):
        """See ICve."""
        return ('http://www.cve.mitre.org/cgi-bin/cvename.cgi?name=%s'
                % self.sequence)

    @property
    def displayname(self):
        return 'CVE-%s' % self.sequence

    @property
    def title(self):
        return 'CVE-%s (%s)' % (self.sequence, self.status.title)

    # CveReference's
    def createReference(self, source, content, url=None):
        """See ICveReference."""
        return CveReference(cve=self, source=source, content=content,
            url=url)

    def removeReference(self, ref):
        assert ref.cve == self
        CveReference.delete(ref.id)

    # Template methods for BugLinkTargetMixin
    buglinkClass = BugCve

    def createBugLink(self, bug):
        """See BugLinkTargetMixin."""
        return BugCve(cve=self, bug=bug)
Esempio n. 3
0
class ChannelBundle(Channel):
    _bundled_channels = SQLRelatedJoin('Channel')

    @property
    def bundled_channels(self):
        """
            Fixes getting the bundled channels when using SQLRelatedJoin.
            Do NOT use directly the attribute but use this method instead.
        """
        return ChannelBundle.selectBy(id=self).throughTo._bundled_channels

    def add_channel(self, channel):
        """ Avoids channel duplication in bundled channels. """
        if channel not in self.bundled_channels and self.has_no_cycles(
                list(self.bundled_channels) + [channel]):
            self.addChannel(channel)

    def remove_channel(self, channel):
        if channel in self.bundled_channels:
            self.removeChannel(channel)

    def flatten(self, keep_disabled_channels=False):
        channels_iterable_list = []
        for channel in self.bundled_channels:
            if channel.enabled or keep_disabled_channels:
                channels_iterable_list.append(
                    channel.flatten(
                        keep_disabled_channels=keep_disabled_channels))
        return itertools.chain.from_iterable(channels_iterable_list)

    def get_type_name(self):
        return 'Bundle'

    def has_no_cycles(self, channels, marked=None):
        if marked is None:
            marked = set()
        if self.id in marked:
            raise ValueError('A cycle was found with channel %s' % self.name)
        marked.add(self.id)
        for c in channels:
            if type(c) == ChannelBundle:
                c.has_no_cycles(c.bundled_channels, marked)
        marked.remove(self.id)
        return True
Esempio n. 4
0
class Country(SQLBase):
    """A country."""

    _table = 'Country'

    # default to listing newest first
    _defaultOrder = 'name'

    # db field names
    name = StringCol(dbName='name', unique=True, notNull=True)
    iso3166code2 = StringCol(dbName='iso3166code2', unique=True,
                             notNull=True)
    iso3166code3 = StringCol(dbName='iso3166code3', unique=True,
                             notNull=True)
    title = StringCol(dbName='title', notNull=False, default=DEFAULT)
    description = StringCol(dbName='description')
    continent = ForeignKey(
        dbName='continent', foreignKey='Continent', default=None)
    languages = SQLRelatedJoin(
        'Language', joinColumn='country', otherColumn='language',
        intermediateTable='SpokenIn')
class TranslationGroup(SQLBase):
    """A TranslationGroup."""

    # default to listing alphabetically
    _defaultOrder = 'name'

    # db field names
    name = StringCol(unique=True, alternateID=True, notNull=True)
    title = StringCol(notNull=True)
    summary = StringCol(notNull=True)
    datecreated = UtcDateTimeCol(notNull=True, default=DEFAULT)
    owner = ForeignKey(
        dbName='owner', foreignKey='Person',
        storm_validator=validate_public_person, notNull=True)

    # useful joins
    distributions = SQLMultipleJoin('Distribution',
        joinColumn='translationgroup')
    languages = SQLRelatedJoin('Language', joinColumn='translationgroup',
        intermediateTable='Translator', otherColumn='language')
    translators = SQLMultipleJoin('Translator',
                                  joinColumn='translationgroup')
    translation_guide_url = StringCol(notNull=False, default=None)

    def __getitem__(self, language_code):
        """See `ITranslationGroup`."""
        query = Store.of(self).find(
            Translator,
            Translator.translationgroup == self,
            Translator.languageID == Language.id,
            Language.code == language_code)

        translator = query.one()
        if translator is None:
            raise NotFoundError(language_code)

        return translator

    # used to note additions
    def add(self, content):
        """See ITranslationGroup."""
        return content

    # adding and removing translators
    def remove_translator(self, translator):
        """See ITranslationGroup."""
        Translator.delete(translator.id)

    # get a translator by language or code
    def query_translator(self, language):
        """See ITranslationGroup."""
        return Translator.selectOneBy(language=language,
                                      translationgroup=self)

    @property
    def products(self):
        """See `ITranslationGroup`."""
        # Avoid circular imports.
        from lp.registry.model.product import Product

        return Product.selectBy(translationgroup=self.id, active=True)

    @property
    def projects(self):
        """See `ITranslationGroup`."""
        # Avoid circular imports.
        from lp.registry.model.projectgroup import ProjectGroup

        return ProjectGroup.selectBy(translationgroup=self.id, active=True)

    # A limit of projects to get for the `top_projects`.
    TOP_PROJECTS_LIMIT = 6

    @property
    def top_projects(self):
        """See `ITranslationGroup`."""
        # XXX Danilo 2009-08-25: We should make this list show a list
        # of projects based on the top translations karma (bug #418493).
        goal = self.TOP_PROJECTS_LIMIT
        projects = list(self.distributions[:goal])
        found = len(projects)
        if found < goal:
            projects.extend(
                list(self.projects[:goal - found]))
            found = len(projects)
        if found < goal:
            projects.extend(
                list(self.products[:goal - found]))
        return projects

    @property
    def number_of_remaining_projects(self):
        """See `ITranslationGroup`."""
        total = (
            self.projects.count() +
            self.products.count() +
            self.distributions.count())
        if total > self.TOP_PROJECTS_LIMIT:
            return total - self.TOP_PROJECTS_LIMIT
        else:
            return 0

    def fetchTranslatorData(self):
        """See `ITranslationGroup`."""
        # Fetch Translator, Language, and Person; but also prefetch the
        # icon information.
        using = [
            Translator,
            Language,
            Person,
            LeftJoin(LibraryFileAlias, LibraryFileAlias.id == Person.iconID),
            LeftJoin(
                LibraryFileContent,
                LibraryFileContent.id == LibraryFileAlias.contentID),
            ]
        tables = (
            Translator,
            Language,
            Person,
            LibraryFileAlias,
            LibraryFileContent,
            )
        translator_data = Store.of(self).using(*using).find(
            tables,
            Translator.translationgroup == self,
            Language.id == Translator.languageID,
            Person.id == Translator.translatorID)
        translator_data = translator_data.order_by(Language.englishname)
        mapper = lambda row: row[slice(0, 3)]
        return DecoratedResultSet(translator_data, mapper)

    def fetchProjectsForDisplay(self, user):
        """See `ITranslationGroup`."""
        # Avoid circular imports.
        from lp.registry.model.product import (
            get_precached_products,
            Product,
            ProductSet,
            )
        products = list(IStore(Product).find(
            Product,
            Product.translationgroupID == self.id,
            Product.active == True,
            ProductSet.getProductPrivacyFilter(user),
            ).order_by(Product.display_name))
        get_precached_products(products, need_licences=True)
        icons = bulk.load_related(LibraryFileAlias, products, ['iconID'])
        bulk.load_related(LibraryFileContent, icons, ['contentID'])
        return products

    def fetchProjectGroupsForDisplay(self):
        """See `ITranslationGroup`."""
        # Avoid circular imports.
        from lp.registry.model.projectgroup import ProjectGroup

        using = [
            ProjectGroup,
            LeftJoin(
                LibraryFileAlias, LibraryFileAlias.id == ProjectGroup.iconID),
            LeftJoin(
                LibraryFileContent,
                LibraryFileContent.id == LibraryFileAlias.contentID),
            ]
        tables = (
            ProjectGroup,
            LibraryFileAlias,
            LibraryFileContent,
            )
        project_data = ISlaveStore(ProjectGroup).using(*using).find(
            tables,
            ProjectGroup.translationgroupID == self.id,
            ProjectGroup.active == True).order_by(ProjectGroup.display_name)

        return DecoratedResultSet(project_data, operator.itemgetter(0))

    def fetchDistrosForDisplay(self):
        """See `ITranslationGroup`."""
        # Avoid circular imports.
        from lp.registry.model.distribution import Distribution

        using = [
            Distribution,
            LeftJoin(
                LibraryFileAlias, LibraryFileAlias.id == Distribution.iconID),
            LeftJoin(
                LibraryFileContent,
                LibraryFileContent.id == LibraryFileAlias.contentID),
            ]
        tables = (
            Distribution,
            LibraryFileAlias,
            LibraryFileContent,
            )
        distro_data = ISlaveStore(Distribution).using(*using).find(
            tables, Distribution.translationgroupID == self.id).order_by(
            Distribution.display_name)

        return DecoratedResultSet(distro_data, operator.itemgetter(0))
Esempio n. 6
0
class Message(SQLBase):
    """A message. This is an RFC822-style message, typically it would be
    coming into the bug system, or coming in from a mailing list.
    """

    _table = 'Message'
    _defaultOrder = '-id'
    datecreated = UtcDateTimeCol(notNull=True, default=UTC_NOW)
    subject = StringCol(notNull=False, default=None)
    owner = ForeignKey(dbName='owner',
                       foreignKey='Person',
                       storm_validator=validate_public_person,
                       notNull=False)
    parent = ForeignKey(foreignKey='Message',
                        dbName='parent',
                        notNull=False,
                        default=None)
    rfc822msgid = StringCol(notNull=True)
    bugs = SQLRelatedJoin('Bug',
                          joinColumn='message',
                          otherColumn='bug',
                          intermediateTable='BugMessage')
    _chunks = SQLMultipleJoin('MessageChunk', joinColumn='message')

    @cachedproperty
    def chunks(self):
        return list(self._chunks)

    raw = ForeignKey(foreignKey='LibraryFileAlias', dbName='raw', default=None)
    _bugattachments = SQLMultipleJoin('BugAttachment', joinColumn='_message')

    @cachedproperty
    def bugattachments(self):
        return list(self._bugattachments)

    visible = BoolCol(notNull=True, default=True)

    def __repr__(self):
        return "<Message at 0x%x id=%s>" % (id(self), self.id)

    def __iter__(self):
        """See IMessage.__iter__"""
        return iter(self.chunks)

    def setVisible(self, visible):
        self.visible = visible

    @property
    def title(self):
        """See IMessage."""
        return self.subject

    @property
    def sender(self):
        """See IMessage."""
        return self.owner

    @cachedproperty
    def text_contents(self):
        """See IMessage."""
        return Message.chunks_text(self.chunks)

    @classmethod
    def chunks_text(cls, chunks):
        bits = [unicode(chunk) for chunk in chunks if chunk.content]
        return '\n\n'.join(bits)

    # XXX flacoste 2006-09-08: Bogus attribute only present so that
    # verifyObject doesn't fail. That attribute is part of the
    # interface because it is used as a UI field in MessageAddView
    content = None

    def getAPIParent(self):
        """See `IMessage`."""
        return None
Esempio n. 7
0
class User(ICTVObject):
    username = StringCol(unique=True, default=None, length=255)
    fullname = StringCol(default=None)
    email = StringCol(notNone=True, alternateID=True, length=255)
    super_admin = BoolCol(notNone=True, default=False)
    admin = BoolCol(notNone=True, default=False)
    disabled = BoolCol(notNone=True, default=True)
    capsules = SQLMultipleJoin('Capsule', joinColumn='owner_id')
    screens = SQLRelatedJoin('Screen')
    authorized_channels = SQLRelatedJoin('Channel')
    roles = SQLMultipleJoin('Role', joinColumn='user_id')
    password = StringCol(default=None)  # Used for local login
    reset_secret = StringCol(
        notNone=True)  # Used for local login to reset password
    has_toured = BoolCol(default=False)  # Has the user completed the app tour

    def __init__(self, *args, **kwargs):
        kwargs['reset_secret'] = utils.generate_secret()
        super().__init__(**kwargs)

    # WARN: user.log_name cannot longer be used in templates since the __getattr__ will not be triggered in jinja
    def _get_log_name(self):
        """ Returns a log friendly and unique name for this user. """
        return "%s (%d)" % (self.fullname if self.fullname else self.email,
                            self.id)

    # WARN: user.readable_name cannot longer be used in templates since the __getattr__ will not be triggered in jinja
    def _get_readable_name(self):
        """ Returns a user friendly and unique name for this user. """
        return self.fullname if self.fullname is not None else self.username if self.username is not None else self.email

    # WARN: user.highest_permission_level cannot longer be used in templates since the __getattr__ will not be triggered in jinja
    def _get_highest_permission_level(self):
        """ Return the highest permission level of this user. """
        if self.super_admin:
            return UserPermissions.super_administrator
        if self.admin:
            return UserPermissions.administrator
        highest = UserPermissions.no_permission
        for role in self.roles:
            if role.permission_level not in highest:
                highest = role.permission_level
            if highest == UserPermissions.channel_administrator:
                break
        if self.owns_screen():
            highest = highest | UserPermissions.screen_administrator
        return highest

    def get_channels_with_permission_level(self, permission_level):
        """ Returns the channels which precisely grants this permission level to the user. """
        return Role.selectBy(
            user=self,
            permission_level=UserPermissions.get_permission_string(
                permission_level)).throughTo.channel

    def owns_screen(self):
        """ Return wheter or not this user is owner of screens. """
        return self.screens.count() > 0

    def get_subscriptions_of_owned_screens(self):
        """ Return the subscriptions of the screens possessed by this user. """
        if UserPermissions.administrator in self.highest_permission_level:
            return Subscription.select()
        return self.screens.throughTo.subscriptions

    class sqlmeta:
        table = "user_table"  # prevent table name to collide with reserved keywords of some databases
class Specification(SQLBase, BugLinkTargetMixin, InformationTypeMixin):
    """See ISpecification."""

    implements(ISpecification, IBugLinkTarget, IInformationType)

    _defaultOrder = ['-priority', 'definition_status', 'name', 'id']

    # db field names
    name = StringCol(unique=True, notNull=True)
    title = StringCol(notNull=True)
    summary = StringCol(notNull=True)
    definition_status = EnumCol(schema=SpecificationDefinitionStatus,
                                notNull=True,
                                default=SpecificationDefinitionStatus.NEW)
    priority = EnumCol(schema=SpecificationPriority,
                       notNull=True,
                       default=SpecificationPriority.UNDEFINED)
    _assignee = ForeignKey(dbName='assignee',
                           notNull=False,
                           foreignKey='Person',
                           storm_validator=validate_public_person,
                           default=None)
    _drafter = ForeignKey(dbName='drafter',
                          notNull=False,
                          foreignKey='Person',
                          storm_validator=validate_public_person,
                          default=None)
    _approver = ForeignKey(dbName='approver',
                           notNull=False,
                           foreignKey='Person',
                           storm_validator=validate_public_person,
                           default=None)
    owner = ForeignKey(dbName='owner',
                       foreignKey='Person',
                       storm_validator=validate_public_person,
                       notNull=True)
    datecreated = UtcDateTimeCol(notNull=True, default=DEFAULT)
    product = ForeignKey(dbName='product',
                         foreignKey='Product',
                         notNull=False,
                         default=None)
    productseries = ForeignKey(dbName='productseries',
                               foreignKey='ProductSeries',
                               notNull=False,
                               default=None)
    distribution = ForeignKey(dbName='distribution',
                              foreignKey='Distribution',
                              notNull=False,
                              default=None)
    distroseries = ForeignKey(dbName='distroseries',
                              foreignKey='DistroSeries',
                              notNull=False,
                              default=None)
    goalstatus = EnumCol(schema=SpecificationGoalStatus,
                         notNull=True,
                         default=SpecificationGoalStatus.PROPOSED)
    goal_proposer = ForeignKey(dbName='goal_proposer',
                               notNull=False,
                               foreignKey='Person',
                               storm_validator=validate_public_person,
                               default=None)
    date_goal_proposed = UtcDateTimeCol(notNull=False, default=None)
    goal_decider = ForeignKey(dbName='goal_decider',
                              notNull=False,
                              foreignKey='Person',
                              storm_validator=validate_public_person,
                              default=None)
    date_goal_decided = UtcDateTimeCol(notNull=False, default=None)
    milestone = ForeignKey(dbName='milestone',
                           foreignKey='Milestone',
                           notNull=False,
                           default=None)
    specurl = StringCol(notNull=False, default=None)
    whiteboard = StringCol(notNull=False, default=None)
    direction_approved = BoolCol(notNull=True, default=False)
    man_days = IntCol(notNull=False, default=None)
    implementation_status = EnumCol(
        schema=SpecificationImplementationStatus,
        notNull=True,
        default=SpecificationImplementationStatus.UNKNOWN)
    superseded_by = ForeignKey(dbName='superseded_by',
                               foreignKey='Specification',
                               notNull=False,
                               default=None)
    completer = ForeignKey(dbName='completer',
                           notNull=False,
                           foreignKey='Person',
                           storm_validator=validate_public_person,
                           default=None)
    date_completed = UtcDateTimeCol(notNull=False, default=None)
    starter = ForeignKey(dbName='starter',
                         notNull=False,
                         foreignKey='Person',
                         storm_validator=validate_public_person,
                         default=None)
    date_started = UtcDateTimeCol(notNull=False, default=None)

    # useful joins
    _subscriptions = SQLMultipleJoin('SpecificationSubscription',
                                     joinColumn='specification',
                                     orderBy='id')
    subscribers = SQLRelatedJoin('Person',
                                 joinColumn='specification',
                                 otherColumn='person',
                                 intermediateTable='SpecificationSubscription',
                                 orderBy=['displayname', 'name'])
    sprint_links = SQLMultipleJoin('SprintSpecification',
                                   orderBy='id',
                                   joinColumn='specification')
    sprints = SQLRelatedJoin('Sprint',
                             orderBy='name',
                             joinColumn='specification',
                             otherColumn='sprint',
                             intermediateTable='SprintSpecification')
    bug_links = SQLMultipleJoin('SpecificationBug',
                                joinColumn='specification',
                                orderBy='id')
    bugs = SQLRelatedJoin('Bug',
                          joinColumn='specification',
                          otherColumn='bug',
                          intermediateTable='SpecificationBug',
                          orderBy='id')
    spec_dependency_links = SQLMultipleJoin('SpecificationDependency',
                                            joinColumn='specification',
                                            orderBy='id')

    dependencies = SQLRelatedJoin('Specification',
                                  joinColumn='specification',
                                  otherColumn='dependency',
                                  orderBy='title',
                                  intermediateTable='SpecificationDependency')
    information_type = EnumCol(enum=InformationType,
                               notNull=True,
                               default=InformationType.PUBLIC)

    @cachedproperty
    def linked_branches(self):
        return list(
            Store.of(self).find(
                SpecificationBranch,
                SpecificationBranch.specificationID == self.id).order_by(
                    SpecificationBranch.id))

    def _fetch_children_or_parents(self, join_cond, cond, user):
        from lp.blueprints.model.specificationsearch import (
            get_specification_privacy_filter)
        return list(
            Store.of(self).using(
                Specification,
                Join(SpecificationDependency, join_cond == self.id)).find(
                    Specification, cond == Specification.id,
                    *get_specification_privacy_filter(user)).order_by(
                        Specification.title))

    def getDependencies(self, user=None):
        return self._fetch_children_or_parents(
            SpecificationDependency.specificationID,
            SpecificationDependency.dependencyID, user)

    def getBlockedSpecs(self, user=None):
        return self._fetch_children_or_parents(
            SpecificationDependency.dependencyID,
            SpecificationDependency.specificationID, user)

    def set_assignee(self, person):
        self.subscribeIfAccessGrantNeeded(person)
        self._assignee = person

    def get_assignee(self):
        return self._assignee

    assignee = property(get_assignee, set_assignee)

    def set_drafter(self, person):
        self.subscribeIfAccessGrantNeeded(person)
        self._drafter = person

    def get_drafter(self):
        return self._drafter

    drafter = property(get_drafter, set_drafter)

    def set_approver(self, person):
        self.subscribeIfAccessGrantNeeded(person)
        self._approver = person

    def get_approver(self):
        return self._approver

    approver = property(get_approver, set_approver)

    def subscribeIfAccessGrantNeeded(self, person):
        """Subscribe person if this specification is not public and if
        the person does not already have grants to access the specification.
        """
        if person is None or self.userCanView(person):
            return
        current_user = getUtility(ILaunchBag).user
        self.subscribe(person, subscribed_by=current_user)

    @cachedproperty
    def subscriptions(self):
        """Sort the subscriptions"""
        from lp.registry.model.person import person_sort_key
        return sorted(self._subscriptions,
                      key=lambda sub: person_sort_key(sub.person))

    @property
    def workitems_text(self):
        """See ISpecification."""
        workitems_lines = []

        def get_header_text(milestone):
            if milestone is None:
                return "Work items:"
            else:
                return "Work items for %s:" % milestone.name

        if len(self.work_items) == 0:
            return ''
        milestone = self.work_items[0].milestone
        # Start by appending a header for the milestone of the first work
        # item. After this we're going to write a new header whenever we see a
        # work item with a different milestone.
        workitems_lines.append(get_header_text(milestone))
        for work_item in self.work_items:
            if work_item.milestone != milestone:
                workitems_lines.append("")
                milestone = work_item.milestone
                workitems_lines.append(get_header_text(milestone))
            assignee = work_item.assignee
            if assignee is not None:
                assignee_part = "[%s] " % assignee.name
            else:
                assignee_part = ""
            # work_items are ordered by sequence
            workitems_lines.append(
                "%s%s: %s" %
                (assignee_part, work_item.title, work_item.status.name))
        return "\n".join(workitems_lines)

    @property
    def target(self):
        """See ISpecification."""
        if self.product:
            return self.product
        return self.distribution

    def newWorkItem(self,
                    title,
                    sequence,
                    status=SpecificationWorkItemStatus.TODO,
                    assignee=None,
                    milestone=None):
        """See ISpecification."""
        if milestone is not None:
            assert milestone.target == self.target, (
                "%s does not belong to this spec's target (%s)" %
                (milestone.displayname, self.target.name))
        return SpecificationWorkItem(title=title,
                                     status=status,
                                     specification=self,
                                     assignee=assignee,
                                     milestone=milestone,
                                     sequence=sequence)

    @cachedproperty
    def work_items(self):
        """See ISpecification."""
        return list(self._work_items)

    @property
    def _work_items(self):
        return Store.of(self).find(SpecificationWorkItem,
                                   specification=self,
                                   deleted=False).order_by("sequence")

    def setWorkItems(self, new_work_items):
        field = ISpecification['workitems_text'].bind(self)
        self.updateWorkItems(field.parseAndValidate(new_work_items))

    def _deleteWorkItemsNotMatching(self, titles):
        """Delete all work items whose title does not match the given ones.

        Also set the sequence of those deleted work items to -1.
        """
        title_counts = self._list_to_dict_of_frequency(titles)

        for work_item in self._work_items:
            if (work_item.title not in title_counts
                    or title_counts[work_item.title] == 0):
                work_item.deleted = True

            elif title_counts[work_item.title] > 0:
                title_counts[work_item.title] -= 1

    def _list_to_dict_of_frequency(self, list):
        dictionary = {}
        for item in list:
            if not item in dictionary:
                dictionary[item] = 1
            else:
                dictionary[item] += 1
        return dictionary

    def updateWorkItems(self, new_work_items):
        """See ISpecification."""
        # First mark work items with titles that are no longer present as
        # deleted.
        self._deleteWorkItemsNotMatching(
            [wi['title'] for wi in new_work_items])
        work_items = self._work_items
        # At this point the list of new_work_items is necessarily the same
        # size (or longer) than the list of existing ones, so we can just
        # iterate over it updating the existing items and creating any new
        # ones.
        to_insert = []
        existing_titles = [wi.title for wi in work_items]
        existing_title_count = self._list_to_dict_of_frequency(existing_titles)

        for i, new_wi in enumerate(new_work_items):
            if (new_wi['title'] not in existing_titles
                    or existing_title_count[new_wi['title']] == 0):
                to_insert.append((i, new_wi))
            else:
                existing_title_count[new_wi['title']] -= 1
                # Get an existing work item with the same title and update
                # it to match what we have now.
                existing_wi_index = existing_titles.index(new_wi['title'])
                existing_wi = work_items[existing_wi_index]
                # Mark a work item as dirty - don't use it again this update.
                existing_titles[existing_wi_index] = None
                # Update the sequence to match its current position on the
                # list entered by the user.
                existing_wi.sequence = i
                existing_wi.status = new_wi['status']
                existing_wi.assignee = new_wi['assignee']
                milestone = new_wi['milestone']
                if milestone is not None:
                    assert milestone.target == self.target, (
                        "%s does not belong to this spec's target (%s)" %
                        (milestone.displayname, self.target.name))
                existing_wi.milestone = milestone

        for sequence, item in to_insert:
            self.newWorkItem(item['title'], sequence, item['status'],
                             item['assignee'], item['milestone'])
        del get_property_cache(self).work_items

    def setTarget(self, target):
        """See ISpecification."""
        if IProduct.providedBy(target):
            self.product = target
            self.distribution = None
        elif IDistribution.providedBy(target):
            self.product = None
            self.distribution = target
        else:
            raise AssertionError("Unknown target: %s" % target)

    def retarget(self, target):
        """See ISpecification."""
        if self.target == target:
            return

        self.validateMove(target)

        # We must lose any goal we have set and approved/declined because we
        # are moving to a different target that will have different
        # policies and drivers.
        self.productseries = None
        self.distroseries = None
        self.goalstatus = SpecificationGoalStatus.PROPOSED
        self.goal_proposer = None
        self.date_goal_proposed = None
        self.milestone = None

        self.setTarget(target)
        self.priority = SpecificationPriority.UNDEFINED
        self.direction_approved = False

    def validateMove(self, target):
        """See ISpecification."""
        if target.getSpecification(self.name) is not None:
            raise TargetAlreadyHasSpecification(target, self.name)

    @property
    def goal(self):
        """See ISpecification."""
        if self.productseries:
            return self.productseries
        return self.distroseries

    def proposeGoal(self, goal, proposer):
        """See ISpecification."""
        if goal is None:
            # we are clearing goals
            self.productseries = None
            self.distroseries = None
        elif (IProductSeries.providedBy(goal) and goal.product == self.target):
            # set the product series as a goal
            self.productseries = goal
            self.goal_proposer = proposer
            self.date_goal_proposed = UTC_NOW
            # and make sure there is no leftover distroseries goal
            self.distroseries = None
        elif (IDistroSeries.providedBy(goal)
              and goal.distribution == self.target):
            # set the distroseries goal
            self.distroseries = goal
            self.goal_proposer = proposer
            self.date_goal_proposed = UTC_NOW
            # and make sure there is no leftover distroseries goal
            self.productseries = None
        else:
            raise GoalProposeError('Inappropriate goal.')
        # record who made the proposal, and when
        self.goal_proposer = proposer
        self.date_goal_proposed = UTC_NOW
        # and of course set the goal status to PROPOSED
        self.goalstatus = SpecificationGoalStatus.PROPOSED
        # the goal should now also not have a decider
        self.goal_decider = None
        self.date_goal_decided = None
        if goal is not None and goal.personHasDriverRights(proposer):
            self.acceptBy(proposer)

    def acceptBy(self, decider):
        """See ISpecification."""
        self.goalstatus = SpecificationGoalStatus.ACCEPTED
        self.goal_decider = decider
        self.date_goal_decided = UTC_NOW

    def declineBy(self, decider):
        """See ISpecification."""
        self.goalstatus = SpecificationGoalStatus.DECLINED
        self.goal_decider = decider
        self.date_goal_decided = UTC_NOW

    def getSprintSpecification(self, sprintname):
        """See ISpecification."""
        for sprintspecification in self.sprint_links:
            if sprintspecification.sprint.name == sprintname:
                return sprintspecification
        return None

    def notificationRecipientAddresses(self):
        """See ISpecification."""
        related_people = [
            self.owner, self.assignee, self.approver, self.drafter
        ]
        related_people = [
            person for person in related_people if person is not None
        ]
        subscribers = [
            subscription.person for subscription in self.subscriptions
        ]
        notify_people = set(related_people + subscribers)
        without_access = set(
            getUtility(IService,
                       'sharing').getPeopleWithoutAccess(self, notify_people))
        notify_people -= without_access
        addresses = set()
        for person in notify_people:
            addresses.update(get_contact_email_addresses(person))
        return sorted(addresses)

    # emergent properties
    @property
    def is_incomplete(self):
        """See ISpecification."""
        return not self.is_complete

    @property
    def is_complete(self):
        """See `ISpecification`."""
        # Implemented blueprints are by definition complete.
        if (self.implementation_status ==
                SpecificationImplementationStatus.IMPLEMENTED):
            return True
        # Obsolete and superseded blueprints are considered complete.
        if self.definition_status in (
                SpecificationDefinitionStatus.OBSOLETE,
                SpecificationDefinitionStatus.SUPERSEDED):
            return True
        # Approved information blueprints are also considered complete.
        if ((self.implementation_status
             == SpecificationImplementationStatus.INFORMATIONAL)
                and (self.definition_status
                     == SpecificationDefinitionStatus.APPROVED)):
            return True
        else:
            return False

    @property
    def is_started(self):
        """See ISpecification. This is a code implementation of the
        SQL in spec_started_clause
        """
        return (self.implementation_status not in [
            SpecificationImplementationStatus.UNKNOWN,
            SpecificationImplementationStatus.NOTSTARTED,
            SpecificationImplementationStatus.DEFERRED,
            SpecificationImplementationStatus.INFORMATIONAL,
        ] or ((self.implementation_status
               == SpecificationImplementationStatus.INFORMATIONAL) and
              (self.definition_status
               == SpecificationDefinitionStatus.APPROVED)))

    @property
    def lifecycle_status(self):
        """Combine the is_complete and is_started emergent properties."""
        if self.is_complete:
            return SpecificationLifecycleStatus.COMPLETE
        elif self.is_started:
            return SpecificationLifecycleStatus.STARTED
        else:
            return SpecificationLifecycleStatus.NOTSTARTED

    def setDefinitionStatus(self, definition_status, user):
        self.definition_status = definition_status
        self.updateLifecycleStatus(user)

    def setImplementationStatus(self, implementation_status, user):
        self.implementation_status = implementation_status
        self.updateLifecycleStatus(user)

    def updateLifecycleStatus(self, user):
        """See ISpecification."""
        newstatus = None
        if self.is_started:
            if self.starterID is None:
                newstatus = SpecificationLifecycleStatus.STARTED
                self.date_started = UTC_NOW
                self.starter = user
        else:
            if self.starterID is not None:
                newstatus = SpecificationLifecycleStatus.NOTSTARTED
                self.date_started = None
                self.starter = None
        if self.is_complete:
            if self.completerID is None:
                newstatus = SpecificationLifecycleStatus.COMPLETE
                self.date_completed = UTC_NOW
                self.completer = user
        else:
            if self.completerID is not None:
                self.date_completed = None
                self.completer = None
                if self.is_started:
                    newstatus = SpecificationLifecycleStatus.STARTED
                else:
                    newstatus = SpecificationLifecycleStatus.NOTSTARTED

        return newstatus

    @property
    def is_blocked(self):
        """See ISpecification."""
        for spec in self.dependencies:
            if spec.is_incomplete:
                return True
        return False

    @property
    def has_accepted_goal(self):
        """See ISpecification."""
        if (self.goal is not None
                and self.goalstatus == SpecificationGoalStatus.ACCEPTED):
            return True
        return False

    def getDelta(self, old_spec, user):
        """See ISpecification."""
        delta = ObjectDelta(old_spec, self)
        delta.recordNewValues(("title", "summary", "specurl", "productseries",
                               "distroseries", "milestone"))
        delta.recordNewAndOld(
            ("name", "priority", "definition_status", "target", "approver",
             "assignee", "drafter", "whiteboard", "workitems_text"))
        delta.recordListAddedAndRemoved("bugs", "bugs_linked", "bugs_unlinked")

        if delta.changes:
            changes = delta.changes
            changes["specification"] = self
            changes["user"] = user

            return SpecificationDelta(**changes)
        else:
            return None

    @property
    def informational(self):
        """For backwards compatibility:
        implemented as a value in implementation_status.
        """
        return (self.implementation_status ==
                SpecificationImplementationStatus.INFORMATIONAL)

    # subscriptions
    def subscription(self, person):
        """See ISpecification."""
        return SpecificationSubscription.selectOneBy(specification=self,
                                                     person=person)

    def getSubscriptionByName(self, name):
        """See ISpecification."""
        for sub in self.subscriptions:
            if sub.person.name == name:
                return sub
        return None

    def subscribe(self, person, subscribed_by=None, essential=False):
        """See ISpecification."""
        if subscribed_by is None:
            subscribed_by = person
        # Create or modify a user's subscription to this blueprint.
        # First see if a relevant subscription exists, and if so, return it
        sub = self.subscription(person)
        if sub is not None:
            if sub.essential != essential:
                # If a subscription already exists, but the value for
                # 'essential' changes, there's no need to create a new
                # subscription, but we modify the existing subscription
                # and notify the user about the change.
                sub.essential = essential
                # The second argument should really be a copy of sub with
                # only the essential attribute changed, but we know
                # that we can get away with not examining the attribute
                # at all - it's a boolean!
                notify(
                    ObjectModifiedEvent(sub,
                                        sub, ['essential'],
                                        user=subscribed_by))
            return sub
        # since no previous subscription existed, create and return a new one
        sub = SpecificationSubscription(specification=self,
                                        person=person,
                                        essential=essential)
        property_cache = get_property_cache(self)
        if 'subscription' in property_cache:
            from lp.registry.model.person import person_sort_key
            property_cache.subscriptions.append(sub)
            property_cache.subscriptions.sort(
                key=lambda sub: person_sort_key(sub.person))
        if self.information_type in PRIVATE_INFORMATION_TYPES:
            # Grant the subscriber access if they can't see the
            # specification.
            service = getUtility(IService, 'sharing')
            ignored, ignored, shared_specs = service.getVisibleArtifacts(
                person, specifications=[self], ignore_permissions=True)
            if not shared_specs:
                service.ensureAccessGrants([person],
                                           subscribed_by,
                                           specifications=[self])
        notify(ObjectCreatedEvent(sub, user=subscribed_by))
        return sub

    def unsubscribe(self, person, unsubscribed_by, ignore_permissions=False):
        """See ISpecification."""
        # see if a relevant subscription exists, and if so, delete it
        if person is None:
            person = unsubscribed_by
        for sub in self.subscriptions:
            if sub.person.id == person.id:
                if (not sub.canBeUnsubscribedByUser(unsubscribed_by)
                        and not ignore_permissions):
                    raise UserCannotUnsubscribePerson(
                        '%s does not have permission to unsubscribe %s.' %
                        (unsubscribed_by.displayname, person.displayname))
                get_property_cache(self).subscriptions.remove(sub)
                SpecificationSubscription.delete(sub.id)
                artifacts_to_delete = getUtility(IAccessArtifactSource).find(
                    [self])
                getUtility(IAccessArtifactGrantSource).revokeByArtifact(
                    artifacts_to_delete, [person])
                return

    def isSubscribed(self, person):
        """See lp.blueprints.interfaces.specification.ISpecification."""
        if person is None:
            return False

        return bool(self.subscription(person))

    # Template methods for BugLinkTargetMixin
    buglinkClass = SpecificationBug

    def createBugLink(self, bug):
        """See BugLinkTargetMixin."""
        return SpecificationBug(specification=self, bug=bug)

    # sprint linking
    def linkSprint(self, sprint, user):
        """See ISpecification."""
        from lp.blueprints.model.sprintspecification import (
            SprintSpecification)
        for sprint_link in self.sprint_links:
            # sprints have unique names
            if sprint_link.sprint.name == sprint.name:
                return sprint_link
        sprint_link = SprintSpecification(specification=self,
                                          sprint=sprint,
                                          registrant=user)
        if sprint.isDriver(user):
            sprint_link.acceptBy(user)
        return sprint_link

    def unlinkSprint(self, sprint):
        """See ISpecification."""
        from lp.blueprints.model.sprintspecification import (
            SprintSpecification)
        for sprint_link in self.sprint_links:
            # sprints have unique names
            if sprint_link.sprint.name == sprint.name:
                SprintSpecification.delete(sprint_link.id)
                return sprint_link

    # dependencies
    def createDependency(self, specification):
        """See ISpecification."""
        for deplink in self.spec_dependency_links:
            if deplink.dependency.id == specification.id:
                return deplink
        return SpecificationDependency(specification=self,
                                       dependency=specification)

    def removeDependency(self, specification):
        """See ISpecification."""
        # see if a relevant dependency link exists, and if so, delete it
        for deplink in self.spec_dependency_links:
            if deplink.dependency.id == specification.id:
                SpecificationDependency.delete(deplink.id)
                return deplink

    def all_deps(self, user=None):
        return list(
            Store.of(self).with_(
                SQL(recursive_dependent_query(user), params=(self.id, ))).find(
                    Specification,
                    Specification.id != self.id,
                    Specification.id.is_in(SQL('select id from dependencies')),
                ).order_by(Specification.name, Specification.id))

    def all_blocked(self, user=None):
        """See `ISpecification`."""
        return list(
            Store.of(self).with_(
                SQL(recursive_blocked_query(user), params=(self.id, ))).find(
                    Specification,
                    Specification.id != self.id,
                    Specification.id.is_in(SQL('select id from blocked')),
                ).order_by(Specification.name, Specification.id))

    # branches
    def getBranchLink(self, branch):
        return SpecificationBranch.selectOneBy(specificationID=self.id,
                                               branchID=branch.id)

    def linkBranch(self, branch, registrant):
        branch_link = self.getBranchLink(branch)
        if branch_link is not None:
            return branch_link
        branch_link = SpecificationBranch(specification=self,
                                          branch=branch,
                                          registrant=registrant)
        Store.of(self).flush()
        del get_property_cache(self).linked_branches
        notify(ObjectCreatedEvent(branch_link))
        return branch_link

    def unlinkBranch(self, branch, user):
        spec_branch = self.getBranchLink(branch)
        spec_branch.destroySelf()
        Store.of(self).flush()
        del get_property_cache(self).linked_branches

    def getLinkedBugTasks(self, user):
        """See `ISpecification`."""
        params = BugTaskSearchParams(user=user, linked_blueprints=self.id)
        tasks = getUtility(IBugTaskSet).search(params)
        if self.distroseries is not None:
            context = self.distroseries
        elif self.distribution is not None:
            context = self.distribution
        elif self.productseries is not None:
            context = self.productseries
        else:
            context = self.product
        return filter_bugtasks_by_context(context, tasks)

    def __repr__(self):
        return '<Specification %s %r for %r>' % (self.id, self.name,
                                                 self.target.name)

    def getAllowedInformationTypes(self, who):
        """See `ISpecification`."""
        return self.target.getAllowedSpecificationInformationTypes()

    def transitionToInformationType(self, information_type, who):
        """See ISpecification."""
        # avoid circular imports.
        from lp.registry.model.accesspolicy import (
            reconcile_access_for_artifact, )
        if self.information_type == information_type:
            return False
        if information_type not in self.getAllowedInformationTypes(who):
            raise CannotChangeInformationType("Forbidden by project policy.")
        self.information_type = information_type
        reconcile_access_for_artifact(self, information_type, [self.target])
        if information_type in PRIVATE_INFORMATION_TYPES and self.subscribers:
            # Grant the subscribers access if they do not have a
            # policy grant.
            service = getUtility(IService, 'sharing')
            blind_subscribers = service.getPeopleWithoutAccess(
                self, self.subscribers)
            if len(blind_subscribers):
                service.ensureAccessGrants(blind_subscribers,
                                           who,
                                           specifications=[self],
                                           ignore_permissions=True)
        return True

    @cachedproperty
    def _known_viewers(self):
        """A set of known persons able to view the specifcation."""
        return set()

    def userCanView(self, user):
        """See `ISpecification`."""
        # Avoid circular imports.
        from lp.blueprints.model.specificationsearch import (
            get_specification_privacy_filter)
        if self.information_type in PUBLIC_INFORMATION_TYPES:
            return True
        if user is None:
            return False
        if user.id in self._known_viewers:
            return True

        if not Store.of(self).find(
                Specification, Specification.id == self.id,
                *get_specification_privacy_filter(user)).is_empty():
            self._known_viewers.add(user.id)
            return True
        return False
Esempio n. 9
0
class SRThrough3(SQLObject):
    name = StringCol()
    ones = SQLMultipleJoin('SRThrough1', joinColumn='threeID')
    twos = SQLRelatedJoin('SRThrough2')
Esempio n. 10
0
class SRThrough2(SQLObject):
    one = ForeignKey('SRThrough1')
    threes = SQLRelatedJoin('SRThrough3', addRemoveName='Three')
class SBPerson(SQLObject):
    name = StringCol()
    addresses = SQLMultipleJoin('SBAddress', joinColumn='personID')
    sharedAddresses = SQLRelatedJoin('SBAddress',
                                     addRemoveName='SharedAddress')
class SBAddress(SQLObject):
    city = StringCol()
    person = ForeignKey('SBPerson')
    sharedPeople = SQLRelatedJoin('SBPerson')
Esempio n. 13
0
class DistroArchSeries(SQLBase):
    _table = 'DistroArchSeries'
    _defaultOrder = 'id'

    distroseries = ForeignKey(dbName='distroseries',
        foreignKey='DistroSeries', notNull=True)
    processor_id = Int(name='processor', allow_none=False)
    processor = Reference(processor_id, Processor.id)
    architecturetag = StringCol(notNull=True)
    official = BoolCol(notNull=True)
    owner = ForeignKey(
        dbName='owner', foreignKey='Person',
        storm_validator=validate_public_person, notNull=True)
    package_count = IntCol(notNull=True, default=DEFAULT)
    enabled = BoolCol(notNull=False, default=True)

    packages = SQLRelatedJoin('BinaryPackageRelease',
        joinColumn='distroarchseries',
        intermediateTable='BinaryPackagePublishing',
        otherColumn='binarypackagerelease')

    def __getitem__(self, name):
        return self.getBinaryPackage(name)

    @property
    def title(self):
        """See `IDistroArchSeries`."""
        return '%s for %s (%s)' % (
            self.distroseries.title, self.architecturetag, self.processor.name)

    @property
    def displayname(self):
        """See `IDistroArchSeries`."""
        return '%s %s %s' % (
            self.distroseries.distribution.displayname,
            self.distroseries.displayname, self.architecturetag)

    @property
    def supports_virtualized(self):
        return self.processor.supports_virtualized

    def updatePackageCount(self):
        """See `IDistroArchSeries`."""
        from lp.soyuz.model.publishing import BinaryPackagePublishingHistory

        query = """
            BinaryPackagePublishingHistory.distroarchseries = %s AND
            BinaryPackagePublishingHistory.archive IN %s AND
            BinaryPackagePublishingHistory.status = %s AND
            BinaryPackagePublishingHistory.pocket = %s
            """ % sqlvalues(
                    self,
                    self.distroseries.distribution.all_distro_archive_ids,
                    PackagePublishingStatus.PUBLISHED,
                    PackagePublishingPocket.RELEASE
                 )
        self.package_count = BinaryPackagePublishingHistory.select(
            query).count()

    @property
    def isNominatedArchIndep(self):
        """See `IDistroArchSeries`."""
        return (self.distroseries.nominatedarchindep is not None and
                self.id == self.distroseries.nominatedarchindep.id)

    def getPocketChroot(self, pocket, exact_pocket=False, image_type=None):
        """See `IDistroArchSeries`."""
        if image_type is None:
            image_type = BuildBaseImageType.CHROOT
        pockets = [pocket] if exact_pocket else pocket_dependencies[pocket]
        pocket_chroots = {
            pocket_chroot.pocket: pocket_chroot
            for pocket_chroot in IStore(PocketChroot).find(
                PocketChroot,
                PocketChroot.distroarchseries == self,
                PocketChroot.pocket.is_in(pockets),
                PocketChroot.image_type == image_type)}
        for pocket_dep in reversed(pockets):
            if pocket_dep in pocket_chroots:
                pocket_chroot = pocket_chroots[pocket_dep]
                # We normally only return a PocketChroot row that is
                # actually populated with a chroot, but if exact_pocket is
                # set then we return even an unpopulated row in order to
                # avoid constraint violations in addOrUpdateChroot.
                if pocket_chroot.chroot is not None or exact_pocket:
                    return pocket_chroot
        return None

    def getChroot(self, default=None, pocket=None, image_type=None):
        """See `IDistroArchSeries`."""
        if pocket is None:
            pocket = PackagePublishingPocket.RELEASE
        pocket_chroot = self.getPocketChroot(pocket, image_type=image_type)

        if pocket_chroot is None:
            return default

        return pocket_chroot.chroot

    def getChrootURL(self, pocket=None, image_type=None):
        """See `IDistroArchSeries`."""
        chroot = self.getChroot(pocket=pocket, image_type=image_type)
        if chroot is None:
            return None
        return chroot.http_url

    @property
    def chroot_url(self):
        """See `IDistroArchSeries`."""
        return self.getChrootURL()

    def addOrUpdateChroot(self, chroot, pocket=None, image_type=None):
        """See `IDistroArchSeries`."""
        if pocket is None:
            pocket = PackagePublishingPocket.RELEASE
        if image_type is None:
            image_type = BuildBaseImageType.CHROOT
        pocket_chroot = self.getPocketChroot(
            pocket, exact_pocket=True, image_type=image_type)

        if pocket_chroot is None:
            return PocketChroot(
                distroarchseries=self, pocket=pocket, chroot=chroot,
                image_type=image_type)
        else:
            pocket_chroot.chroot = chroot

        return pocket_chroot

    def setChroot(self, data, sha1sum, pocket=None, image_type=None):
        """See `IDistroArchSeries`."""
        # XXX: StevenK 2013-06-06 bug=1116954: We should not need to refetch
        # the file content from the request, since the passed in one has been
        # wrongly encoded.
        data = get_raw_form_value_from_current_request(data, 'data')
        if isinstance(data, str):
            filecontent = data
        else:
            filecontent = data.read()

        # Due to http://bugs.python.org/issue1349106 launchpadlib sends
        # MIME with \n line endings, which is illegal. lazr.restful
        # parses each ending as \r\n, resulting in a binary that ends
        # with \r getting the last byte chopped off. To cope with this
        # on the server side we try to append \r if the SHA-1 doesn't
        # match.
        content_sha1sum = hashlib.sha1(filecontent).hexdigest()
        if content_sha1sum != sha1sum:
            filecontent += '\r'
            content_sha1sum = hashlib.sha1(filecontent).hexdigest()
        if content_sha1sum != sha1sum:
            raise InvalidChrootUploaded("Chroot upload checksums do not match")

        # This duplicates addOrUpdateChroot, but we need it to build a
        # reasonable filename.
        if image_type is None:
            image_type = BuildBaseImageType.CHROOT

        filename = '%s-%s-%s-%s.tar.gz' % (
            image_type.name.lower().split()[0],
            self.distroseries.distribution.name, self.distroseries.name,
            self.architecturetag)
        lfa = getUtility(ILibraryFileAliasSet).create(
            name=filename, size=len(filecontent), file=StringIO(filecontent),
            contentType='application/octet-stream')
        if lfa.content.sha1 != sha1sum:
            raise InvalidChrootUploaded("Chroot upload checksums do not match")
        self.addOrUpdateChroot(lfa, pocket=pocket, image_type=image_type)

    def setChrootFromBuild(self, livefsbuild, filename, pocket=None,
                           image_type=None):
        """See `IDistroArchSeries`."""
        self.addOrUpdateChroot(
            livefsbuild.getFileByName(filename), pocket=pocket,
            image_type=image_type)

    def removeChroot(self, pocket=None, image_type=None):
        """See `IDistroArchSeries`."""
        self.addOrUpdateChroot(None, pocket=pocket, image_type=image_type)

    def searchBinaryPackages(self, text):
        """See `IDistroArchSeries`."""
        from lp.soyuz.model.publishing import BinaryPackagePublishingHistory

        origin = [
            BinaryPackageRelease,
            Join(
                BinaryPackagePublishingHistory,
                BinaryPackagePublishingHistory.binarypackagerelease ==
                    BinaryPackageRelease.id),
            Join(
                BinaryPackageName,
                BinaryPackageRelease.binarypackagename ==
                    BinaryPackageName.id)]

        find_spec = [BinaryPackageRelease, BinaryPackageName]
        archives = self.distroseries.distribution.getArchiveIDList()

        clauses = [
            BinaryPackagePublishingHistory.distroarchseries == self,
            BinaryPackagePublishingHistory.archiveID.is_in(archives),
            BinaryPackagePublishingHistory.status.is_in(
                active_publishing_status)]
        order_by = [BinaryPackageName.name]
        if text:
            ranking = rank_by_fti(BinaryPackageRelease, text)
            find_spec.append(ranking)
            clauses.append(
                Or(
                    fti_search(BinaryPackageRelease, text),
                    BinaryPackageName.name.contains_string(text.lower())))
            order_by.insert(0, ranking)
        result = IStore(BinaryPackageName).using(*origin).find(
            tuple(find_spec), *clauses).config(distinct=True).order_by(
                *order_by)

        # import here to avoid circular import problems
        from lp.soyuz.model.distroarchseriesbinarypackagerelease import (
            DistroArchSeriesBinaryPackageRelease)

        # Create a function that will decorate the results, converting
        # them from the find_spec above into DASBPRs.
        def result_to_dasbpr(row):
            return DistroArchSeriesBinaryPackageRelease(
                distroarchseries=self, binarypackagerelease=row[0])

        # Return the decorated result set so the consumer of these
        # results will only see DSPs.
        return DecoratedResultSet(result, result_to_dasbpr)

    def getBinaryPackage(self, name):
        """See `IDistroArchSeries`."""
        from lp.soyuz.model.distroarchseriesbinarypackage import (
            DistroArchSeriesBinaryPackage,
            )
        if not IBinaryPackageName.providedBy(name):
            try:
                name = BinaryPackageName.byName(name)
            except SQLObjectNotFound:
                return None
        return DistroArchSeriesBinaryPackage(
            self, name)

    def getBuildRecords(self, build_state=None, name=None, pocket=None,
                        arch_tag=None, user=None, binary_only=True):
        """See IHasBuildRecords"""
        # Ignore "user", since it would not make any difference to the
        # records returned here (private builds are only in PPA right
        # now).
        # Ignore "binary_only" as for a distro arch series it is only
        # the binaries that are relevant.

        # For consistency we return an empty resultset if arch_tag
        # is provided but doesn't match our architecture.
        if arch_tag is not None and arch_tag != self.architecturetag:
            return EmptyResultSet()

        # Use the facility provided by IBinaryPackageBuildSet to
        # retrieve the records.
        return getUtility(IBinaryPackageBuildSet).getBuildsForDistro(
            self, build_state, name, pocket)

    @property
    def main_archive(self):
        return self.distroseries.distribution.main_archive
Esempio n. 14
0
class Language(SQLBase):

    _table = 'Language'

    code = StringCol(dbName='code',
                     notNull=True,
                     unique=True,
                     alternateID=True)
    uuid = StringCol(dbName='uuid', notNull=False, default=None)
    nativename = StringCol(dbName='nativename')
    englishname = StringCol(dbName='englishname')
    pluralforms = IntCol(dbName='pluralforms')
    pluralexpression = StringCol(dbName='pluralexpression')
    visible = BoolCol(dbName='visible', notNull=True)
    direction = EnumCol(dbName='direction',
                        notNull=True,
                        schema=TextDirection,
                        default=TextDirection.LTR)

    translation_teams = SQLRelatedJoin('Person',
                                       joinColumn="language",
                                       intermediateTable='Translator',
                                       otherColumn='translator')

    _countries = SQLRelatedJoin('Country',
                                joinColumn='language',
                                otherColumn='country',
                                intermediateTable='SpokenIn')

    # Define a read/write property `countries` so it can be passed
    # to language administration `LaunchpadFormView`.
    def _getCountries(self):
        return self._countries

    def _setCountries(self, countries):
        for country in self._countries:
            if country not in countries:
                self.removeCountry(country)
        for country in countries:
            if country not in self._countries:
                self.addCountry(country)

    countries = property(_getCountries, _setCountries)

    @property
    def displayname(self):
        """See `ILanguage`."""
        return '%s (%s)' % (self.englishname, self.code)

    def __repr__(self):
        return "<%s '%s' (%s)>" % (self.__class__.__name__, self.englishname,
                                   self.code)

    @property
    def guessed_pluralforms(self):
        """See `ILanguage`."""
        forms = self.pluralforms
        if forms is None:
            # Just take a plausible guess.  The caller needs a number.
            return 2
        else:
            return forms

    @property
    def alt_suggestion_language(self):
        """See `ILanguage`.

        Non-visible languages and English are not translatable, so they
        are excluded. Brazilian Portuguese has diverged from Portuguese
        to such a degree that it should be treated as a parent language.
        Norwegian languages Nynorsk (nn) and Bokmaal (nb) are similar
        and may provide suggestions for each other.
        """
        if self.code == 'pt_BR':
            return None
        elif self.code == 'nn':
            return Language.byCode('nb')
        elif self.code == 'nb':
            return Language.byCode('nn')
        codes = self.code.split('_')
        if len(codes) == 2 and codes[0] != 'en':
            language = Language.byCode(codes[0])
            if language.visible == True:
                return language
            else:
                return None
        return None

    @property
    def dashedcode(self):
        """See `ILanguage`."""
        return self.code.replace('_', '-')

    @property
    def abbreviated_text_dir(self):
        """See `ILanguage`."""
        if self.direction == TextDirection.LTR:
            return 'ltr'
        elif self.direction == TextDirection.RTL:
            return 'rtl'
        else:
            assert False, "unknown text direction"

    @property
    def translators(self):
        """See `ILanguage`."""
        from lp.registry.model.person import (
            Person,
            PersonLanguage,
        )
        return IStore(Language).using(
            Join(Person, LanguageSet._getTranslatorJoins(),
                 Person.id == PersonLanguage.personID), ).find(
                     Person,
                     PersonLanguage.language == self,
                 ).order_by(Desc(KarmaCache.karmavalue))

    @cachedproperty
    def translators_count(self):
        """See `ILanguage`."""
        return self.translators.count()
Esempio n. 15
0
class Channel(InheritableSQLObject):
    name = StringCol(notNone=True, unique=True)
    description = StringCol(default=None)
    enabled = BoolCol(notNone=True, default=True)
    subscription_right = EnumCol(enumValues=['public', 'restricted', 'private'])
    authorized_subscribers = SQLRelatedJoin('User')
    secret = StringCol(notNone=True, default=lambda: utils.generate_secret())
    subscriptions = SQLMultipleJoin('Subscription')
    bundles = SQLRelatedJoin('ChannelBundle')

    def can_subscribe(self, user):
        """ Return whether this user has sufficient permission to be able to subscribe to this channel or not. """
        return self.subscription_right == 'public' or UserPermissions.administrator in user.highest_permission_level or user in self.authorized_subscribers

    def safe_add_user(self, user):
        """ Avoid user duplication in channel authorized subscribers. """
        if user not in self.authorized_subscribers:
            self.addUser(user)

    @classmethod
    def get_channels_authorized_subscribers_as_json(cls, channels):
        """
            Return the string representation of a dictionary in the form
                {
                    channel.id:
                    [
                        user.id,
                        ...
                    ]
                }
        """
        channels_authorized_subscribers = {}
        for channel in channels:
            channels_authorized_subscribers[channel.id] = [u.id for u in channel.authorized_subscribers]
        return json.dumps(channels_authorized_subscribers)

    @classmethod
    def get_visible_channels_of(cls, user):
        """
            Returns the channels that are accessible for the user (public channels or channels
            with the user specified in authorized_subscribers, or all channels if the user is superadmin)
            :param user: The user to retrieve the accessible channels.
            :return: A iterable with the accessible channels (iterable of sqlobjects)
        """
        if UserPermissions.administrator in user.highest_permission_level:
            return set(Channel.select())
        public_channels = set(Channel.selectBy(subscription_right='public')) if UserPermissions.screen_administrator in \
                                                                                user.highest_permission_level else set()
        return public_channels | set(Role.selectBy(user=user).throughTo.channel) | set(
            User.selectBy(id=user.id).throughTo.authorized_channels)

    @classmethod
    def get_screens_channels_from(cls, user):
        """
            Return the intersection between 3 sets of channels: all the public channels,
            all the channel this user is authorized to subscribe
            and all the channel the screens this user has access are subscribed to.
            The resulting data type is a set of Channel instances.
        """
        if user.super_admin:
            return set(Channel.select())
        return set(c for c in Channel.selectBy(subscription_right='public')) | \
               set(c for c in user.authorized_channels) | \
               set(c for c in user.screens.throughTo.subscriptions.throughTo.channel)

    def get_preview_link(self):
        """ Returns the secret preview link of this channel. """
        return '/preview/channels/' + str(self.id) + '/' + self.secret

    @abstractmethod
    def flatten(self, keep_disabled_channels=False):
        """ Returns all the channels contained in this channel and the channels it contains as an Iterable[Channel]"""

    @abstractmethod
    def get_type_name(self):
        """ Returns a string representing the name of the subtype to be used in the UI for this class. """
Esempio n. 16
0
class DistroArchSeries(SQLBase):
    implements(IDistroArchSeries, IHasBuildRecords, ICanPublishPackages)
    _table = 'DistroArchSeries'
    _defaultOrder = 'id'

    distroseries = ForeignKey(dbName='distroseries',
                              foreignKey='DistroSeries',
                              notNull=True)
    processor_id = Int(name='processor', allow_none=False)
    processor = Reference(processor_id, Processor.id)
    architecturetag = StringCol(notNull=True)
    official = BoolCol(notNull=True)
    owner = ForeignKey(dbName='owner',
                       foreignKey='Person',
                       storm_validator=validate_public_person,
                       notNull=True)
    package_count = IntCol(notNull=True, default=DEFAULT)
    supports_virtualized = BoolCol(notNull=False, default=False)
    enabled = BoolCol(notNull=False, default=True)

    packages = SQLRelatedJoin('BinaryPackageRelease',
                              joinColumn='distroarchseries',
                              intermediateTable='BinaryPackagePublishing',
                              otherColumn='binarypackagerelease')

    def __getitem__(self, name):
        return self.getBinaryPackage(name)

    @property
    def title(self):
        """See `IDistroArchSeries`."""
        return '%s for %s (%s)' % (self.distroseries.title,
                                   self.architecturetag, self.processor.name)

    @property
    def displayname(self):
        """See `IDistroArchSeries`."""
        return '%s %s %s' % (self.distroseries.distribution.displayname,
                             self.distroseries.displayname,
                             self.architecturetag)

    def updatePackageCount(self):
        """See `IDistroArchSeries`."""
        from lp.soyuz.model.publishing import BinaryPackagePublishingHistory

        query = """
            BinaryPackagePublishingHistory.distroarchseries = %s AND
            BinaryPackagePublishingHistory.archive IN %s AND
            BinaryPackagePublishingHistory.status = %s AND
            BinaryPackagePublishingHistory.pocket = %s
            """ % sqlvalues(
            self, self.distroseries.distribution.all_distro_archive_ids,
            PackagePublishingStatus.PUBLISHED, PackagePublishingPocket.RELEASE)
        self.package_count = BinaryPackagePublishingHistory.select(
            query).count()

    @property
    def isNominatedArchIndep(self):
        """See `IDistroArchSeries`."""
        return (self.distroseries.nominatedarchindep
                and self.id == self.distroseries.nominatedarchindep.id)

    def getPocketChroot(self):
        """See `IDistroArchSeries`."""
        pchroot = PocketChroot.selectOneBy(distroarchseries=self)
        return pchroot

    def getChroot(self, default=None):
        """See `IDistroArchSeries`."""
        pocket_chroot = self.getPocketChroot()

        if pocket_chroot is None:
            return default

        return pocket_chroot.chroot

    @property
    def chroot_url(self):
        """See `IDistroArchSeries`."""
        chroot = self.getChroot()
        if chroot is None:
            return None
        return chroot.http_url

    def addOrUpdateChroot(self, chroot):
        """See `IDistroArchSeries`."""
        pocket_chroot = self.getPocketChroot()

        if pocket_chroot is None:
            return PocketChroot(distroarchseries=self, chroot=chroot)
        else:
            pocket_chroot.chroot = chroot

        return pocket_chroot

    def setChroot(self, data, sha1sum):
        """See `IDistroArchSeries`."""
        # XXX: StevenK 2013-06-06 bug=1116954: We should not need to refetch
        # the file content from the request, since the passed in one has been
        # wrongly encoded.
        data = get_raw_form_value_from_current_request('data')
        if isinstance(data, str):
            filecontent = data
        else:
            filecontent = data.read()
        filename = 'chroot-%s-%s-%s.tar.bz2' % (
            self.distroseries.distribution.name, self.distroseries.name,
            self.architecturetag)
        lfa = getUtility(ILibraryFileAliasSet).create(
            name=filename,
            size=len(filecontent),
            file=StringIO(filecontent),
            contentType='application/octet-stream')
        if lfa.content.sha1 != sha1sum:
            raise InvalidChrootUploaded("Chroot upload checksums do not match")
        self.addOrUpdateChroot(lfa)

    def removeChroot(self):
        """See `IDistroArchSeries`."""
        self.addOrUpdateChroot(None)

    def searchBinaryPackages(self, text):
        """See `IDistroArchSeries`."""
        from lp.soyuz.model.publishing import BinaryPackagePublishingHistory

        origin = [
            BinaryPackageRelease,
            Join(
                BinaryPackagePublishingHistory,
                BinaryPackagePublishingHistory.binarypackagerelease ==
                BinaryPackageRelease.id),
            Join(
                BinaryPackageName,
                BinaryPackageRelease.binarypackagename == BinaryPackageName.id)
        ]

        find_spec = [BinaryPackageRelease, BinaryPackageName]
        archives = self.distroseries.distribution.getArchiveIDList()

        clauses = [
            BinaryPackagePublishingHistory.distroarchseries == self,
            BinaryPackagePublishingHistory.archiveID.is_in(archives),
            BinaryPackagePublishingHistory.dateremoved == None
        ]
        order_by = [BinaryPackageName.name]
        if text:
            ranking = rank_by_fti(BinaryPackageRelease, text)
            find_spec.append(ranking)
            clauses.append(
                Or(fti_search(BinaryPackageRelease, text),
                   BinaryPackageName.name.contains_string(text.lower())))
            order_by.insert(0, ranking)
        result = IStore(BinaryPackageName).using(*origin).find(
            tuple(find_spec),
            *clauses).config(distinct=True).order_by(*order_by)

        # import here to avoid circular import problems
        from lp.soyuz.model.distroarchseriesbinarypackagerelease import (
            DistroArchSeriesBinaryPackageRelease)

        # Create a function that will decorate the results, converting
        # them from the find_spec above into DASBPRs.
        def result_to_dasbpr(row):
            return DistroArchSeriesBinaryPackageRelease(
                distroarchseries=self, binarypackagerelease=row[0])

        # Return the decorated result set so the consumer of these
        # results will only see DSPs.
        return DecoratedResultSet(result, result_to_dasbpr)

    def getBinaryPackage(self, name):
        """See `IDistroArchSeries`."""
        from lp.soyuz.model.distroarchseriesbinarypackage import (
            DistroArchSeriesBinaryPackage, )
        if not IBinaryPackageName.providedBy(name):
            try:
                name = BinaryPackageName.byName(name)
            except SQLObjectNotFound:
                return None
        return DistroArchSeriesBinaryPackage(self, name)

    def getBuildRecords(self,
                        build_state=None,
                        name=None,
                        pocket=None,
                        arch_tag=None,
                        user=None,
                        binary_only=True):
        """See IHasBuildRecords"""
        # Ignore "user", since it would not make any difference to the
        # records returned here (private builds are only in PPA right
        # now).
        # Ignore "binary_only" as for a distro arch series it is only
        # the binaries that are relevant.

        # For consistency we return an empty resultset if arch_tag
        # is provided but doesn't match our architecture.
        if arch_tag is not None and arch_tag != self.architecturetag:
            return EmptyResultSet()

        # Use the facility provided by IBinaryPackageBuildSet to
        # retrieve the records.
        return getUtility(IBinaryPackageBuildSet).getBuildsForDistro(
            self, build_state, name, pocket)

    def getReleasedPackages(self,
                            binary_name,
                            pocket=None,
                            include_pending=False,
                            archive=None):
        """See IDistroArchSeries."""
        from lp.soyuz.model.publishing import BinaryPackagePublishingHistory

        queries = []

        if not IBinaryPackageName.providedBy(binary_name):
            binary_name = BinaryPackageName.byName(binary_name)

        queries.append("""
        binarypackagerelease=binarypackagerelease.id AND
        binarypackagerelease.binarypackagename=%s AND
        distroarchseries = %s
        """ % sqlvalues(binary_name, self))

        if pocket is not None:
            queries.append("pocket=%s" % sqlvalues(pocket.value))

        if include_pending:
            queries.append("status in (%s, %s)" %
                           sqlvalues(PackagePublishingStatus.PUBLISHED,
                                     PackagePublishingStatus.PENDING))
        else:
            queries.append("status=%s" %
                           sqlvalues(PackagePublishingStatus.PUBLISHED))

        archives = self.distroseries.distribution.getArchiveIDList(archive)
        queries.append("archive IN %s" % sqlvalues(archives))

        published = BinaryPackagePublishingHistory.select(
            " AND ".join(queries),
            clauseTables=['BinaryPackageRelease'],
            orderBy=['-id'])

        return shortlist(published)

    def getPendingPublications(self, archive, pocket, is_careful):
        """See `ICanPublishPackages`."""
        from lp.soyuz.model.publishing import BinaryPackagePublishingHistory

        queries = [
            "distroarchseries = %s AND archive = %s" %
            sqlvalues(self, archive)
        ]

        target_status = [PackagePublishingStatus.PENDING]
        if is_careful:
            target_status.append(PackagePublishingStatus.PUBLISHED)
        queries.append("status IN %s" % sqlvalues(target_status))

        # restrict to a specific pocket.
        queries.append('pocket = %s' % sqlvalues(pocket))

        # Exclude RELEASE pocket if the distroseries was already released,
        # since it should not change, unless the archive allows it.
        if (not self.distroseries.isUnstable()
                and not archive.allowUpdatesToReleasePocket()):
            queries.append('pocket != %s' %
                           sqlvalues(PackagePublishingPocket.RELEASE))

        publications = BinaryPackagePublishingHistory.select(
            " AND ".join(queries), orderBy=["-id"])

        return publications

    def publish(self, diskpool, log, archive, pocket, is_careful=False):
        """See `ICanPublishPackages`."""
        log.debug("Attempting to publish pending binaries for %s" %
                  self.architecturetag)

        dirty_pockets = set()

        for bpph in self.getPendingPublications(archive, pocket, is_careful):
            if not self.distroseries.checkLegalPocket(bpph, is_careful, log):
                continue
            bpph.publish(diskpool, log)
            dirty_pockets.add((self.distroseries.name, bpph.pocket))

        return dirty_pockets

    @property
    def main_archive(self):
        return self.distroseries.distribution.main_archive
Esempio n. 17
0
class LibraryFileAlias(SQLBase):
    """A filename and mimetype that we can serve some given content with."""

    _table = 'LibraryFileAlias'
    date_created = UtcDateTimeCol(notNull=False, default=DEFAULT)
    content = ForeignKey(
        foreignKey='LibraryFileContent',
        dbName='content',
        notNull=False,
    )
    filename = StringCol(notNull=True)
    mimetype = StringCol(notNull=True)
    expires = UtcDateTimeCol(notNull=False, default=None)
    restricted = BoolCol(notNull=True, default=False)
    hits = IntCol(notNull=True, default=0)

    products = SQLRelatedJoin('ProductRelease',
                              joinColumn='libraryfile',
                              otherColumn='productrelease',
                              intermediateTable='ProductReleaseFile')

    sourcepackages = SQLRelatedJoin(
        'SourcePackageRelease',
        joinColumn='libraryfile',
        otherColumn='sourcepackagerelease',
        intermediateTable='SourcePackageReleaseFile')

    @property
    def client(self):
        """Return the librarian client to use to retrieve that file."""
        if self.restricted:
            return getUtility(IRestrictedLibrarianClient)
        else:
            return getUtility(ILibrarianClient)

    @property
    def http_url(self):
        """See ILibraryFileAlias.http_url"""
        return self.client.getURLForAliasObject(self)

    @property
    def https_url(self):
        """See ILibraryFileAlias.https_url"""
        url = self.http_url
        if url is None:
            return url
        return url.replace('http', 'https', 1)

    @property
    def private_url(self):
        """See ILibraryFileAlias.https_url"""
        return self.client.getURLForAlias(self.id, secure=True)

    def getURL(self, secure=True, include_token=False):
        """See ILibraryFileAlias.getURL"""
        if not self.restricted:
            if config.librarian.use_https and secure:
                return self.https_url
            else:
                return self.http_url
        else:
            url = self.private_url
            if include_token:
                token = TimeLimitedToken.allocate(url)
                url += '?token=%s' % token
            return url

    _datafile = None

    def open(self, timeout=LIBRARIAN_SERVER_DEFAULT_TIMEOUT):
        """See ILibraryFileAlias."""
        self._datafile = self.client.getFileByAlias(self.id, timeout)
        if self._datafile is None:
            raise DownloadFailed("Unable to retrieve LibraryFileAlias %d" %
                                 self.id)

    def read(self, chunksize=None, timeout=LIBRARIAN_SERVER_DEFAULT_TIMEOUT):
        """See ILibraryFileAlias."""
        if not self._datafile:
            if chunksize is not None:
                raise RuntimeError("Can't combine autoopen with chunksize")
            self.open(timeout=timeout)
            autoopen = True
        else:
            autoopen = False

        if chunksize is None:
            rv = self._datafile.read()
            if autoopen:
                self.close()
            return rv
        else:
            return self._datafile.read(chunksize)

    def close(self):
        # Don't die with an AttributeError if the '_datafile' property
        # is not set.
        if self._datafile is not None:
            self._datafile.close()
            self._datafile = None

    @property
    def last_downloaded(self):
        """See `ILibraryFileAlias`."""
        store = Store.of(self)
        results = store.find(LibraryFileDownloadCount, libraryfilealias=self)
        results.order_by(Desc(LibraryFileDownloadCount.day))
        entry = results.first()
        if entry is None:
            return None
        else:
            return datetime.now(pytz.utc).date() - entry.day

    def updateDownloadCount(self, day, country, count):
        """See ILibraryFileAlias."""
        store = Store.of(self)
        entry = store.find(LibraryFileDownloadCount,
                           libraryfilealias=self,
                           day=day,
                           country=country).one()
        if entry is None:
            entry = LibraryFileDownloadCount(libraryfilealias=self,
                                             day=day,
                                             country=country,
                                             count=count)
        else:
            entry.count += count
        self.hits += count

    products = SQLRelatedJoin('ProductRelease',
                              joinColumn='libraryfile',
                              otherColumn='productrelease',
                              intermediateTable='ProductReleaseFile')

    sourcepackages = SQLRelatedJoin(
        'SourcePackageRelease',
        joinColumn='libraryfile',
        otherColumn='sourcepackagerelease',
        intermediateTable='SourcePackageReleaseFile')

    @property
    def deleted(self):
        return self.contentID is None

    def __storm_invalidated__(self):
        """Make sure that the file is closed across transaction boundary."""
        super(LibraryFileAlias, self).__storm_invalidated__()
        self.close()
Esempio n. 18
0
class Screen(ICTVObject):
    name = StringCol(notNone=True)
    building = ForeignKey('Building', notNone=True, cascade=False)
    location = StringCol(default=None)  # A free text field to precise the screen location
    screen_id = DatabaseIndex('name', 'building', unique=True)
    owners = SQLRelatedJoin('User')
    subscriptions = SQLMultipleJoin('Subscription')
    secret = StringCol(notNone=True, default=utils.generate_secret)
    macs = SQLMultipleJoin('ScreenMac')
    last_ip = StringCol(default=None)
    last_access = DateTimeCol(default=None)
    shuffle = BoolCol(default=False)
    comment = StringCol(default=None)
    show_postit = BoolCol(default=False)
    show_slide_number = BoolCol(default=False)
    orientation = EnumCol(enumValues=['Landscape','Portrait'],default='Landscape')

    @property
    def subscribed_channels(self):
        return self.subscriptions.throughTo.channel

    def subscribe_to(self, user, channel, weight=1):
        """
        Subscribes this screen to the channel. If this screen is already subscribed to the channel by the user, it changes the weight if needed.

        :param user: The user requesting the subscription.
        :param channel: The channel to subscribe this screen to.
        :param weight: The optional positive non null weight to give to the channel by this screen.
        :return: None
        """
        if weight > 0:
            sub = Subscription.selectBy(screen=self, channel=channel).getOne(None)
            if sub is not None:
                if sub.weight != weight:
                    sub.weight = weight
                if sub.created_by != user:
                    sub.created_by = user
            else:
                Subscription(screen=self, channel=channel, weight=weight, created_by=user)

    def unsubscribe_from(self, user, channel):
        """
        Deletes the user's subscription from this screen to the channel if one exists, otherwise do nothing.

        :param user: The user requesting the subscription deletion.
        :param channel: The channel to unsubscribe this screen from.
        :return: None
        """
        sub = Subscription.selectBy(screen=self, channel=channel).getOne(None)
        # TODO: Commit this to user history
        if sub is not None:
            sub.destroySelf()

    def is_subscribed_to(self, channel):
        return channel in self.subscriptions.throughTo.channel

    @classmethod
    def get_visible_screens_of(cls, user):
        """
        Returns the screens that are managed by the user (or all screens for the superadmin)

        :param user: The user to retrieve the managed screens.
        :return: A iterable with the managed screens (iterable of sqlobjects)
        """
        if UserPermissions.administrator in user.highest_permission_level:
            return Screen.select()
        return user.screens

    def safe_add_user(self, user):
        """ Avoid user duplication in screen owners. """
        if user not in self.owners:
            self.addUser(user)

    def get_view_link(self):
        """ Returns a relative URL to the screen rendered view. """
        return '/screens/%d/view/%s' % (self.id, self.secret)

    def get_client_link(self):
        """ Returns a relative URL to web-based client for this screen. """
        return '/screens/%d/client/%s' % (self.id, self.secret)

    def get_macs_string(self):
        return ';'.join(mac.get_pretty_mac() for mac in self.macs)

    def get_channels_content(self, app):
        """ 
            Returns all the capsules provided by the channels of this screen as an Iterable[PluginCapsule] 
            ignoring channel duplicates
        """
        screen_capsules_iterables = []
        already_added_channels = set()
        plugin_manager = app.plugin_manager

        def add_content(channel):
            for c in channel.flatten():
                # do not add duplicates
                if c.id not in already_added_channels:
                    screen_capsules_iterables.append(plugin_manager.get_plugin_content(c))
                    already_added_channels.add(c.id)
        for sub in self.subscriptions:
            add_content(sub.channel)
        screen_capsules = list(itertools.chain.from_iterable(screen_capsules_iterables))
        if self.shuffle:
            random.shuffle(screen_capsules)
        return screen_capsules