示例#1
0
class BaseStormOpenIDAssociation:
    """Database representation of a stored OpenID association."""

    __storm_primary__ = ('server_url', 'handle')

    server_url = Unicode()
    handle = Unicode()
    secret = RawStr()
    issued = Int()
    lifetime = Int()
    assoc_type = Unicode()

    def __init__(self, server_url, association):
        super(BaseStormOpenIDAssociation, self).__init__()
        self.server_url = server_url.decode('UTF-8')
        self.handle = association.handle.decode('ASCII')
        self.update(association)

    def update(self, association):
        assert self.handle == association.handle.decode('ASCII'), (
            "Association handle does not match (expected %r, got %r" %
            (self.handle, association.handle))
        self.secret = association.secret
        self.issued = association.issued
        self.lifetime = association.lifetime
        self.assoc_type = association.assoc_type.decode('ASCII')

    def as_association(self):
        """Return an equivalent openid-python `Association` object."""
        return Association(
            self.handle.encode('ASCII'), self.secret, self.issued,
            self.lifetime, self.assoc_type.encode('ASCII'))
示例#2
0
class XRef(StormBase):
    """Cross-reference between two objects.

    For references to local objects (there is currently no other kind),
    another reference in the opposite direction exists.

    The to_id_int and from_id_int columns exist for efficient SQL joins.
    They are set automatically when the ID looks like an integer.

    NOTE: This should rarely be used directly. Prefer IXRefSet unless
    porting an old query.
    """

    __storm_table__ = 'XRef'
    __storm_primary__ = "from_type", "from_id", "to_type", "to_id"

    to_type = Unicode(allow_none=False)
    to_id = Unicode(allow_none=False)
    to_id_int = Int()  # For efficient joins.
    from_type = Unicode(allow_none=False)
    from_id = Unicode(allow_none=False)
    from_id_int = Int()  # For efficient joins.
    creator_id = Int(name="creator")
    creator = Reference(creator_id, "Person.id")
    date_created = DateTime(name='date_created', tzinfo=pytz.UTC)
    metadata = JSON()
示例#3
0
class Product(BaseModel):

    __storm_table__ = "sfec_product"

    name = Unicode()

    stock = Int(default=0)

    description = Unicode()

    price = Decimal()

    is_available = Bool(default=False)

    categories = ReferenceSet('Product.id', 'CategoryProduct.product_id',
                              'CategoryProduct.category_id', 'Category.id')

    #
    # Implicit Properties
    #

    @property
    def category_list(self):
        return [c.name for c in self.categories]

    def dict(self):
        pdict = super(Product, self).dict()
        pdict['price'] = float(pdict['price'])  # price is decimal
        return pdict
示例#4
0
class MusicTrack(BaseModel):
    '''Music file references and metadata'''

    __storm_table__ = 'musictrack'
    id = Int(primary=True)
    filename = Unicode()
    title = Unicode()
    tracknumber = Int()
    rating = Int()
    length = Int()
    bitrate = Int()
    comment = Unicode()
    lyrics = Unicode()
    album_id = Int()
    album = Reference(album_id, 'MusicAlbum.id')

    def to_dict(self, recurse=True):
        '''See BaseModel.to_dict.'''
        ret = {
            'id': self.id,
            'filename': self.filename,
            'title': self.title,
            'tracknumber': self.tracknumber,
            'rating': self.rating,
            'length': self.length,
            'bitrate': self.bitrate,
            'comment': self.comment,
            'lyrics': self.lyrics
        }
        if recurse:
            ret['album_id'] = self.album_id
            ret['album'] = self.album.to_dict(recurse=False)
        return ret
示例#5
0
class PhotoImage(BaseModel):
    '''Image file references and metadata'''

    __storm_table__ = 'photoimage'
    id = Int(primary=True)
    filename = Unicode()
    thumbnail = Unicode()
    title = Unicode()
    description = Unicode()
    creation_date = DateTime()
    album_id = Int()
    album = Reference(album_id, 'PhotoAlbum.id')

    def to_dict(self, recurse=True):
        '''See BaseModel.to_dict'''
        ret = {
            'id': self.id,
            'filename': self.filename,
            'thumbnail': self.thumbnail,
            'title': self.title,
            'description': self.description,
            'creation_date': self.creation_date,
        }
        if recurse:
            ret['album_id'] = self.album_id
            ret['album'] = self.album.to_dict(recurse=False)
        return ret
示例#6
0
class MusicAlbum(BaseModel):
    '''A music file container

    Music can be categorized many ways.  Albums are found in the music file's
    ID3 tags
    '''

    __storm_table__ = 'musicalbum'
    id = Int(primary=True)
    artist = Unicode()
    title = Unicode()
    year = Int()
    genre = Unicode()
    tracks = ReferenceSet('MusicAlbum.id', 'MusicTrack.album_id')

    def to_dict(self, recurse=True):
        '''See BaseModel.to_dict.'''
        ret = {
            'id': self.id,
            'artist': self.artist,
            'title': self.title,
            'year': self.year,
            'genre': self.genre,
        }
        if recurse:
            ret['tracks'] = [
                track.to_dict(recurse=False) for track in self.tracks
            ]
        return ret
示例#7
0
class User(BaseModel):

    __storm_table__ = "sfec_user"

    name = Unicode()

    # Basic login data
    email = Unicode()
    password = Unicode()

    birth_date = DateTime()
    register_date = DateTime()

    def __init__(self):
        self.register_date = datetime.now()

    #
    # Properties
    #

    @property
    def is_admin(self):
        store = get_default_store()
        return store.find(Admin, id=self.id).one() is not None

    #
    # Static API
    #

    @staticmethod
    def hash(password):
        """The hash function to be used by the table"""
        return unicode(sha512(password).hexdigest())

    @classmethod
    def authenticate(cls, store, email, password):
        """Returns the user that matches the email password combination"""
        pw_hash = cls.hash(password)
        queries = [cls.email == email, cls.password == pw_hash]

        user = store.find(cls, And(*queries)).one()
        if user:
            user.last_login = datetime.now()
            user.last_ip = unicode(request.remote_addr)
            store.commit()
            return user

        return False

    #
    # Public API
    #

    def dict(self):
        super_dict = super(User, self).dict()
        super_dict['is_admin'] = self.is_admin
        return super_dict

    def set_password(self, password):
        self.password = self.hash(password)
示例#8
0
class BaseStormOpenIDNonce:
    """Database representation of a stored OpenID nonce."""
    __storm_primary__ = ('server_url', 'timestamp', 'salt')

    server_url = Unicode()
    timestamp = Int()
    salt = Unicode()

    def __init__(self, server_url, timestamp, salt):
        super(BaseStormOpenIDNonce, self).__init__()
        self.server_url = server_url
        self.timestamp = timestamp
        self.salt = salt
示例#9
0
class VideoMeta(BaseModel):
    '''Extra meta data for different types of videos'''

    __storm_table__ = 'videometa'
    id = Int(primary=True)
    season = Int()
    episode = Int()
    runtime = Int()
    genres = Unicode()
    year = Int()
    plot = Unicode()
    file_id = Int()
    file = Reference(file_id, 'VideoFile.id')
    series_id = Int()
    series = Reference(series_id, VideoSeries.id)
示例#10
0
class Category(Storm):
    __storm_table__ = 'category'

    id = Int(primary=True, default=AutoReload)
    short_name = Unicode()
    name = Unicode()
    total_laps = Int()
    race_id = Int()
    race = Reference(race_id, 'Race.id')

    def update(self):
        self._complete_laps = None
        self._total_racers = None

    @property
    def total_racers(self):
        if hasattr(self, '_total_racers') and self._total_racers is not None:
            return self._total_racers

        store = Store.of(self)
        query = And(Racer.id == RacerLap.racer_id,
                    Racer.category_id == self.id)
        data = store.using(RacerLap, Racer).find((Count(1)), query)
        data = list(data.group_by(Racer.category_id, Racer.id))

        complete_racers = len([i for i in data if i == self.total_laps])
        total_racers = Store.of(self).find(Racer,
                                           Racer.category == self).count()

        self._total_racers = '%s / %s' % (complete_racers, total_racers)
        return self._total_racers

    @property
    def is_last_lap(self):
        return self.completed_laps == (self.total_laps - 1)

    @property
    def completed_laps(self):
        if hasattr(self, '_complete_laps') and self._complete_laps is not None:
            return self._complete_laps

        store = Store.of(self)
        query = And(Racer.id == RacerLap.racer_id,
                    Racer.category_id == self.id)
        data = store.using(RacerLap, Racer).find((Count(1)), query)
        data = data.group_by(Racer.category_id, Racer.id)
        self._complete_laps = max(list(data) or [0])
        return self._complete_laps
示例#11
0
文件: base.py 项目: cogini/storm
class Foo(object):
    """
    Test table.
    """
    __storm_table__ = "foo"
    id = Int(primary=True)
    title = Unicode()
示例#12
0
class Race(Storm):
    __storm_table__ = 'race'

    id = Int(primary=True, default=AutoReload)
    name = Unicode()
    start_time = DateTime()
    end_time = DateTime()

    def start(self):
        now = datetime.datetime.now().replace(microsecond=0)
        self.start_time = now

    def get_categories(self):
        return Store.of(self).find(Category, race=self)

    @cached_property
    def start_hour(self):
        return self.start_time.time()

    @property
    def time_elapsed(self):
        if not self.start_time:
            return None
        delta = datetime.datetime.now() - self.start_time
        return datetime.timedelta(seconds=delta.seconds)
示例#13
0
class SignedCodeOfConduct(SQLBase):
    """Code of Conduct."""

    _table = 'SignedCodeOfConduct'

    owner = ForeignKey(foreignKey="Person", dbName="owner", notNull=True)

    signedcode = StringCol(dbName='signedcode', notNull=False, default=None)

    signing_key_fingerprint = Unicode()

    datecreated = UtcDateTimeCol(dbName='datecreated',
                                 notNull=True,
                                 default=UTC_NOW)

    recipient = ForeignKey(foreignKey="Person",
                           dbName="recipient",
                           notNull=False,
                           default=None)

    admincomment = StringCol(dbName='admincomment',
                             notNull=False,
                             default=None)

    active = BoolCol(dbName='active', notNull=True, default=False)

    @cachedproperty
    def signingkey(self):
        if self.signing_key_fingerprint is not None:
            return getUtility(IGPGKeySet).getByFingerprint(
                self.signing_key_fingerprint)

    @property
    def displayname(self):
        """Build a Fancy Title for CoC."""
        displayname = self.datecreated.strftime('%Y-%m-%d')

        if self.signingkey:
            displayname += (
                ': digitally signed by %s (%s)' %
                (self.owner.displayname, self.signingkey.displayname))
        else:
            displayname += (': paper submission accepted by %s' %
                            self.recipient.displayname)

        return displayname

    def sendAdvertisementEmail(self, subject, content):
        """See ISignedCodeOfConduct."""
        assert self.owner.preferredemail
        template = get_email_template('signedcoc-acknowledge.txt',
                                      app='registry')
        fromaddress = format_address("Launchpad Code Of Conduct System",
                                     config.canonical.noreply_from_address)
        replacements = {'user': self.owner.displayname, 'content': content}
        message = template % replacements
        simple_sendmail(fromaddress, str(self.owner.preferredemail.email),
                        subject, message)
示例#14
0
class VideoFile(BaseModel):
    '''Video file reference and metadata'''

    __storm_table__ = 'videofile'
    id = Int(primary=True)
    filename = Unicode()
    thumb = Unicode()
    type = Unicode()  #Default 'CLIP'

    def to_dict(self, recurse=True):
        '''See BaseModel.to_dict.'''
        ret = {
            'id': self.id,
            'filename': self.filename,
            'thumb': self.thumb,
            'type': self.type
        }
        return ret
示例#15
0
文件: base.py 项目: cogini/storm
class Bar(object):
    """
    Test table referencing to C{Foo}
    """
    __storm_table__ = "bar"
    id = Int(primary=True)
    title = Unicode()
    foo_id = Int()
    foo = DeferredReference(foo_id, Foo.id)
示例#16
0
class VideoSeries(BaseModel):
    '''A video container

    Videos can be grouped by the series they were created for, with their
    accompanying season and episode numbers.  This does not apply to anything
    but television
    '''

    __storm_table__ = 'videoseries'
    id = Int(primary=True)
    title = Unicode()
    actor_1 = Unicode()
    actor_2 = Unicode()
    actor_3 = Unicode()
    actor_4 = Unicode()
    actor_5 = Unicode()
    writer_1 = Unicode()
    writer_2 = Unicode()
    director_1 = Unicode()
    director_2 = Unicode()
示例#17
0
class MusicPlaylist(BaseModel):
    '''A music file container

    Users can create and add music tracks to a playlist
    '''

    __storm_table__ = 'musicplaylist'
    id = Int(primary=True)
    title = Unicode()
    tracks = ReferenceSet('MusicPlaylist.id', 'MusicPlaylistTrack.playlist_id',
                          'MusicPlaylistTrack.track_id', 'MusicTrack.id')
示例#18
0
class Category(BaseModel):

    __storm_table__ = "sfec_category"

    name = Unicode()

    products = ReferenceSet('Category.id', 'CategoryProduct.category_id',
                            'CategoryProduct.product_id', 'Product.id')

    def __init__(self, name):
        self.name = name
示例#19
0
class Order(BaseModel):

    __storm_table__ = "sfec_order"

    status = Unicode()

    products = ReferenceSet('Order.id', 'OrderProduct.order_id')

    user_id = Int()
    user = Reference(user_id, User.id)

    def __init__(self):
        self.status = u"Buying"
示例#20
0
class PhotoAlbum(BaseModel):
    '''A photo group'''

    __storm_table__ = 'photoalbum'
    id = Int(primary=True)
    title = Unicode()
    description = Unicode()
    creation_date = DateTime()
    images = ReferenceSet('PhotoAlbum.id', 'PhotoImage.album_id')

    def to_dict(self, recurse=True):
        '''See BaseModel.to_dict.'''
        ret = {
            'id': self.id,
            'title': self.title,
            'description': self.description,
            'creation_date': self.creation_date,
        }
        if recurse:
            ret['images'] = [
                image.to_dict(recurse=False) for image in self.images
            ]
        return ret
示例#21
0
class MilestoneTag(object):
    """A tag belonging to a milestone."""

    __storm_table__ = 'milestonetag'

    id = Int(primary=True)
    milestone_id = Int(name='milestone', allow_none=False)
    milestone = Reference(milestone_id, 'milestone.id')
    tag = Unicode(allow_none=False)
    created_by_id = Int(name='created_by', allow_none=False)
    created_by = Reference(created_by_id, 'person.id')
    date_created = DateTime(allow_none=False)

    def __init__(self, milestone, tag, created_by, date_created=None):
        self.milestone_id = milestone.id
        self.tag = tag
        self.created_by_id = created_by.id
        if date_created is not None:
            self.date_created = date_created
示例#22
0
class BugSubscriptionFilterTag(StormBase):
    """Tags to filter."""

    __storm_table__ = "BugSubscriptionFilterTag"

    id = Int(primary=True)

    filter_id = Int("filter", allow_none=False)
    filter = Reference(filter_id, "BugSubscriptionFilter.id")

    include = Bool(allow_none=False)
    tag = Unicode(allow_none=False)

    @property
    def qualified_tag(self):
        """The tag qualified with a hyphen if it is to be omitted."""
        if self.include:
            return self.tag
        else:
            return u"-" + self.tag
示例#23
0
class BugSummary(Storm):
    """BugSummary Storm database class."""

    implements(IBugSummary)

    __storm_table__ = 'combinedbugsummary'

    id = Int(primary=True)
    count = Int()

    product_id = Int(name='product')
    product = Reference(product_id, Product.id)

    productseries_id = Int(name='productseries')
    productseries = Reference(productseries_id, ProductSeries.id)

    distribution_id = Int(name='distribution')
    distribution = Reference(distribution_id, Distribution.id)

    distroseries_id = Int(name='distroseries')
    distroseries = Reference(distroseries_id, DistroSeries.id)

    sourcepackagename_id = Int(name='sourcepackagename')
    sourcepackagename = Reference(sourcepackagename_id, SourcePackageName.id)

    milestone_id = Int(name='milestone')
    milestone = Reference(milestone_id, Milestone.id)

    status = EnumCol(dbName='status',
                     schema=(BugTaskStatus, BugTaskStatusSearch))

    importance = EnumCol(dbName='importance', schema=BugTaskImportance)

    tag = Unicode()

    viewed_by_id = Int(name='viewed_by')
    viewed_by = Reference(viewed_by_id, Person.id)
    access_policy_id = Int(name='access_policy')
    access_policy = Reference(access_policy_id, AccessPolicy.id)

    has_patch = Bool()
示例#24
0
class Racer(Storm):
    __storm_table__ = 'racer'

    id = Int(primary=True)
    number = Int()
    name = Unicode()
    category_id = Int()
    category = Reference(category_id, 'Category.id')

    race_id = Int()
    race = Reference(race_id, 'Race.id')

    def update(self):
        self._complete_laps = None
        self._last_lap = -1

    @property
    def last_lap(self):
        if hasattr(self, '_last_lap') and self._last_lap != -1:
            return self._last_lap

        store = Store.of(self)
        query = (RacerLap.racer_id == self.id)
        self._last_lap = store.find(RacerLap, query).order_by(
            RacerLap.event_time).last()
        return self._last_lap

    @property
    def is_finished(self):
        last_lap = self.last_lap
        if last_lap is None:
            return False
        return last_lap.remaining_laps == 0

    @property
    def completed_laps(self):
        if hasattr(self, '_complete_laps') and self._complete_laps is not None:
            return self._complete_laps

        self._complete_laps = self.get_laps().count()
        return self._complete_laps

    @property
    def total_time(self):
        last = self.last_lap
        if not last:
            return None
        delta = last.event_time - self.race.start_time
        return str(datetime.timedelta(seconds=delta.seconds))

    @cached_property
    def category_str(self):
        return self.category.name

    def add_lap(self):
        lap = RacerLap()
        lap.racer = self
        lap.race = self.race
        lap.event_time = datetime.datetime.now()
        Store.of(self).add(lap)
        return lap

    def get_laps(self, before=None):
        store = Store.of(self)
        query = (RacerLap.racer == self)
        if before is not None:
            query = And(query, RacerLap.event_time < before)
        return store.find(RacerLap, query).order_by(RacerLap.event_time)
示例#25
0
class UserData(object):
    """
        User database table abstraction.
        """

    __storm_table__ = 'goliat_user'
    id = Int(primary=True)
    username = Unicode(allow_none=False)
    password = Unicode(allow_none=False)
    groups = Unicode()
    created_on = DateTime()
    last_login = DateTime()
    is_active = Bool(value_factory=True)
    superuser = Bool(value_factory=False)

    def __init__(self, username='', password=''):
        """
            Initialized the object.
            """
        super(UserData, self).__init__()
        self.username = unicode(username)
        self.password = unicode(password)

    def set_username(self, username):
        """
            Set the username.
            """
        self.username = unicode(username)

    def set_password(self, password):
        """
            Set the password.
            """
        self.password = unicode(password)

    def set_groups(self, groups=[]):
        """
            Set the groups
            """
        self.groups = unicode(','.join(groups))

    def set_creation_date(self):
        """
            Set the creation datetime.
            """
        self.created_on = datetime.datetime.now()

    def set_last_login(self):
        """
            Set the last login datetime.
            """
        self.last_login = datetime.datetime.now()

    def activate(self):
        """
            Activate the user.
            """
        self.is_active = True

    def deactivate(self):
        """
            Deactivate the user.
            """
        self.is_active = False

    def set_superuser(self, value):
        """
            Set the superuser flag.
            """
        self.superuser = value
示例#26
0
class BugSubscriptionFilter(StormBase):
    """A filter to specialize a *structural* subscription."""

    implements(IBugSubscriptionFilter)

    __storm_table__ = "BugSubscriptionFilter"

    id = Int(primary=True)

    structural_subscription_id = Int("structuralsubscription",
                                     allow_none=False)
    structural_subscription = Reference(structural_subscription_id,
                                        "StructuralSubscription.id")

    bug_notification_level = DBEnum(enum=BugNotificationLevel,
                                    default=BugNotificationLevel.COMMENTS,
                                    allow_none=False)
    find_all_tags = Bool(allow_none=False, default=False)
    include_any_tags = Bool(allow_none=False, default=False)
    exclude_any_tags = Bool(allow_none=False, default=False)

    other_parameters = Unicode()

    description = Unicode('description')

    def _get_collection(self, cls, attribute):
        kind = getattr(cls, attribute)
        return frozenset(
            IStore(cls).find(cls, cls.filter == self).values(kind))

    def _set_collection(self, cls, enum, attribute, current_set, desired_set):
        desired_set = frozenset(desired_set)
        if desired_set == frozenset(enum.items):
            # Setting all is the same as setting none, and setting none is
            # cheaper for reading and storage.
            desired_set = frozenset()
        # Add missing.
        store = IStore(cls)
        for kind in desired_set.difference(current_set):
            bsf = cls()
            bsf.filter = self
            setattr(bsf, attribute, kind)
            store.add(bsf)
        # Remove unused.
        kind = getattr(cls, attribute)
        store.find(cls, cls.filter == self,
                   kind.is_in(current_set.difference(desired_set))).remove()

    def _get_statuses(self):
        return self._get_collection(BugSubscriptionFilterStatus, 'status')

    def _set_statuses(self, statuses):
        self._set_collection(BugSubscriptionFilterStatus, BugTaskStatus,
                             'status', self.statuses, statuses)

    statuses = property(_get_statuses,
                        _set_statuses,
                        doc=("A frozenset of statuses filtered on."))

    def _get_importances(self):
        return self._get_collection(BugSubscriptionFilterImportance,
                                    'importance')

    def _set_importances(self, importances):
        self._set_collection(BugSubscriptionFilterImportance,
                             BugTaskImportance, 'importance', self.importances,
                             importances)

    importances = property(_get_importances,
                           _set_importances,
                           doc=("A frozenset of importances filtered on."))

    def _get_tags(self):
        """Return a frozenset of tags to filter on."""
        wildcards = []
        if self.include_any_tags:
            wildcards.append(u"*")
        if self.exclude_any_tags:
            wildcards.append(u"-*")
        tags = (tag_filter.qualified_tag
                for tag_filter in IStore(BugSubscriptionFilterTag).find(
                    BugSubscriptionFilterTag, BugSubscriptionFilterTag.filter
                    == self))
        return frozenset(chain(wildcards, tags))

    def _set_tags(self, tags):
        """Update the tags to filter on.

        The tags can be qualified with a leading hyphen, and can be bundled in
        any iterable.

        If they are passed within a `searchbuilder.any` or `searchbuilder.all`
        object, the `find_all_tags` attribute will be updated to match.

        Wildcard tags - `*` and `-*` - can be given too, and will update
        `include_any_tags` and `exclude_any_tags`.
        """
        # Deal with searchbuilder terms.
        if isinstance(tags, searchbuilder.all):
            self.find_all_tags = True
            tags = frozenset(tags.query_values)
        elif isinstance(tags, searchbuilder.any):
            self.find_all_tags = False
            tags = frozenset(tags.query_values)
        else:
            # Leave find_all_tags unchanged.
            tags = frozenset(tags)
        wildcards = frozenset((u"*", u"-*")).intersection(tags)
        # Set wildcards.
        self.include_any_tags = "*" in wildcards
        self.exclude_any_tags = "-*" in wildcards
        # Deal with other tags.
        tags = tags - wildcards
        store = IStore(BugSubscriptionFilterTag)
        current_tag_filters = dict(
            (tag_filter.qualified_tag, tag_filter)
            for tag_filter in store.find(
                BugSubscriptionFilterTag, BugSubscriptionFilterTag.filter ==
                self))
        # Remove unused tags.
        for tag in set(current_tag_filters).difference(tags):
            tag_filter = current_tag_filters.pop(tag)
            store.remove(tag_filter)
        # Add additional tags.
        for tag in tags.difference(current_tag_filters):
            tag_filter = BugSubscriptionFilterTag()
            tag_filter.filter = self
            tag_filter.include = not tag.startswith("-")
            tag_filter.tag = tag.lstrip("-")
            store.add(tag_filter)

    tags = property(_get_tags,
                    _set_tags,
                    doc=("A frozenset of tags filtered on."))

    def _get_information_types(self):
        return self._get_collection(BugSubscriptionFilterInformationType,
                                    'information_type')

    def _set_information_types(self, information_types):
        self._set_collection(BugSubscriptionFilterInformationType,
                             InformationType, 'information_type',
                             self.information_types, information_types)

    information_types = property(
        _get_information_types,
        _set_information_types,
        doc=("A frozenset of information_types filtered on."))

    def delete(self):
        """See `IBugSubscriptionFilter`."""
        BugSubscriptionFilter.deleteMultiple([self.id])
        Store.of(self).remove(self)

    @classmethod
    def deleteMultiple(cls, ids):
        from lp.bugs.model.structuralsubscription import StructuralSubscription
        store = IStore(BugSubscriptionFilter)
        structsub_ids = list(
            store.find(BugSubscriptionFilter.structural_subscription_id,
                       BugSubscriptionFilter.id.is_in(ids)))
        kinds = [
            BugSubscriptionFilterImportance, BugSubscriptionFilterStatus,
            BugSubscriptionFilterTag, BugSubscriptionFilterInformationType
        ]
        for kind in kinds:
            store.find(kind, kind.filter_id.is_in(ids)).remove()
        store.find(BugSubscriptionFilter,
                   BugSubscriptionFilter.id.is_in(ids)).remove()
        # Now delete any structural subscriptions that have no filters.
        # Take out a SHARE lock on the filters that we use as evidence
        # for keeping structsubs, to ensure that they haven't been
        # deleted under us.
        filter_expr = Select(
            1,
            tables=[BugSubscriptionFilter],
            where=(BugSubscriptionFilter.structural_subscription_id ==
                   StructuralSubscription.id))
        locked_filter_expr = SQL(
            convert_storm_clause_to_string(filter_expr) + ' FOR SHARE')
        store.find(StructuralSubscription,
                   StructuralSubscription.id.is_in(structsub_ids),
                   Not(Exists(locked_filter_expr))).remove()

    def isMuteAllowed(self, person):
        """See `IBugSubscriptionFilter`."""
        subscriber = self.structural_subscription.subscriber
        # The person can mute the Subscription if the subscription is via a
        # team of which they are a member and the team doesn't have a contact
        # address (because if the team does, then the mute would be
        # ineffectual).
        return (subscriber.is_team and person.inTeam(subscriber)
                and subscriber.preferredemail is None)

    def muted(self, person):
        store = Store.of(self)
        existing_mutes = store.find(
            BugSubscriptionFilterMute,
            BugSubscriptionFilterMute.filter_id == self.id,
            BugSubscriptionFilterMute.person_id == person.id)
        if not existing_mutes.is_empty():
            return existing_mutes.one().date_created

    def mute(self, person):
        """See `IBugSubscriptionFilter`."""
        subscriber = self.structural_subscription.subscriber
        if subscriber.is_team and subscriber.preferredemail:
            raise MuteNotAllowed(
                "This subscription cannot be muted because team %s has a "
                "contact address." % subscriber.name)
        if not self.isMuteAllowed(person):
            raise MuteNotAllowed("This subscription cannot be muted for %s" %
                                 person.name)

        store = Store.of(self)
        existing_mutes = store.find(
            BugSubscriptionFilterMute,
            BugSubscriptionFilterMute.filter_id == self.id,
            BugSubscriptionFilterMute.person_id == person.id)
        if existing_mutes.is_empty():
            mute = BugSubscriptionFilterMute()
            mute.person = person
            mute.filter = self.id
            store.add(mute)

    def unmute(self, person):
        """See `IBugSubscriptionFilter`."""
        store = Store.of(self)
        existing_mutes = store.find(
            BugSubscriptionFilterMute,
            BugSubscriptionFilterMute.filter_id == self.id,
            BugSubscriptionFilterMute.person_id == person.id)
        existing_mutes.remove()