class Tourtment(SQLObject): class sqlmeta: table = 'competition' # test on a non-standard way name = StringCol() fightersAsList = RelatedJoin('Fighter') fightersAsSResult = SQLRelatedJoin('Fighter')
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)
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
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))
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
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
class SRThrough3(SQLObject): name = StringCol() ones = SQLMultipleJoin('SRThrough1', joinColumn='threeID') twos = SQLRelatedJoin('SRThrough2')
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')
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
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()
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. """
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
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()
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