class Event(Model): VERSION = 2 MIGRATOR = EventMigrator # key is event_id event = VumiMessage(TransportEvent) message = ForeignKey(OutboundMessage) batches = ManyToMany(Batch) # Extra fields for compound indexes message_with_status = Unicode(index=True, null=True) batches_with_statuses_reverse = ListOf(Unicode(), index=True) def save(self): # We override this method to set our index fields before saving. timestamp = self.event['timestamp'] if not isinstance(timestamp, basestring): timestamp = format_vumi_date(timestamp) status = self.event['event_type'] if status == "delivery_report": status = "%s.%s" % (status, self.event['delivery_status']) self.message_with_status = u"%s$%s$%s" % (self.message.key, timestamp, status) self.batches_with_statuses_reverse = [] reverse_ts = to_reverse_timestamp(timestamp) for batch_id in self.batches.keys(): self.batches_with_statuses_reverse.append( u"%s$%s$%s" % (batch_id, reverse_ts, status)) return super(Event, self).save()
class CurrentTag(Model): # key is flattened tag current_batch = ForeignKey(Batch, null=True) tag = Tag() metadata = Dynamic(Unicode()) @staticmethod def _flatten_tag(tag): return "%s:%s" % tag @staticmethod def _split_key(key): return tuple(key.split(':', 1)) @classmethod def _tag_and_key(cls, tag_or_key): if isinstance(tag_or_key, tuple): # key looks like a tag tag, key = tag_or_key, cls._flatten_tag(tag_or_key) else: tag, key = cls._split_key(tag_or_key), tag_or_key return tag, key def __init__(self, manager, key, _riak_object=None, **kw): tag, key = self._tag_and_key(key) if _riak_object is None: kw['tag'] = tag super(CurrentTag, self).__init__(manager, key, _riak_object=_riak_object, **kw) @classmethod def load(cls, manager, key, result=None): _tag, key = cls._tag_and_key(key) return super(CurrentTag, cls).load(manager, key, result)
class ContactV1(Model): """A contact""" bucket = "contact" VERSION = 1 MIGRATOR = ContactMigrator # key is UUID user_account = ForeignKey(UserAccount) name = Unicode(max_length=255, null=True) surname = Unicode(max_length=255, null=True) email_address = Unicode(null=True) # EmailField? msisdn = Unicode(max_length=255) dob = Timestamp(null=True) twitter_handle = Unicode(max_length=100, null=True) facebook_id = Unicode(max_length=100, null=True) bbm_pin = Unicode(max_length=100, null=True) gtalk_id = Unicode(null=True) mxit_id = Unicode(null=True) wechat_id = Unicode(null=True) created_at = Timestamp(default=datetime.utcnow) groups = ManyToMany(ContactGroupVNone) extra = Dynamic(prefix='extras-') subscription = Dynamic(prefix='subscription-') def add_to_group(self, group): if isinstance(group, ContactGroupVNone): self.groups.add(group) else: self.groups.add_key(group) def addr_for(self, delivery_class): if delivery_class is None: # FIXME: Find a better way to do get delivery_class and get rid of # this hack. return self.msisdn # TODO: delivery classes need to be defined somewhere if delivery_class in ('sms', 'ussd'): return self.msisdn elif delivery_class == 'gtalk': return self.gtalk_id elif delivery_class == 'twitter': return self.twitter_handle elif delivery_class == 'mxit': return self.mxit_id elif delivery_class == 'wechat': return self.wechat_id else: return None def __unicode__(self): if self.name and self.surname: return u' '.join([self.name, self.surname]) else: return (self.surname or self.name or self.gtalk_id or self.twitter_handle or self.msisdn or self.mxit_id or self.wechat_id or 'Unknown User')
class Contact(Model): """A contact""" VERSION = 2 MIGRATOR = ContactMigrator # key is UUID user_account = ForeignKey(UserAccount) name = Unicode(max_length=255, null=True) surname = Unicode(max_length=255, null=True) email_address = Unicode(null=True) # EmailField? dob = Timestamp(null=True) created_at = Timestamp(default=datetime.utcnow) groups = ManyToMany(ContactGroup) extra = Dynamic(prefix='extras-') subscription = Dynamic(prefix='subscription-') # Address fields msisdn = Unicode(max_length=255, index=True) twitter_handle = Unicode(max_length=100, null=True, index=True) facebook_id = Unicode(max_length=100, null=True, index=True) bbm_pin = Unicode(max_length=100, null=True, index=True) gtalk_id = Unicode(null=True, index=True) mxit_id = Unicode(null=True, index=True) wechat_id = Unicode(null=True, index=True) def add_to_group(self, group): if isinstance(group, ContactGroup): self.groups.add(group) else: self.groups.add_key(group) def addr_for(self, delivery_class): if delivery_class is None: # FIXME: Find a better way to do get delivery_class and get rid of # this hack. return self.msisdn delivery_class = DELIVERY_CLASSES.get(delivery_class) if delivery_class is not None: return getattr(self, delivery_class['field']) return None def __unicode__(self): if self.name and self.surname: return u' '.join([self.name, self.surname]) else: return (self.surname or self.name or self.gtalk_id or self.twitter_handle or self.msisdn or self.mxit_id or self.wechat_id or 'Unknown User')
class ContactGroup(Model): """A group of contacts""" # key is UUID name = Unicode() query = Unicode(null=True) user_account = ForeignKey(UserAccount) created_at = Timestamp(default=datetime.utcnow) @Manager.calls_manager def add_contacts(self, contacts, save=True): for contact in contacts: contact.groups.add(self) yield contact.save() def is_smart_group(self): return self.query is not None def __unicode__(self): return self.name
class Event(Model): VERSION = 1 MIGRATOR = EventMigrator # key is event_id event = VumiMessage(TransportEvent) message = ForeignKey(OutboundMessage) # Extra fields for compound indexes message_with_status = Unicode(index=True, null=True) def save(self): # We override this method to set our index fields before saving. timestamp = self.event['timestamp'] status = self.event['event_type'] if status == "delivery_report": status = "%s.%s" % (status, self.event['delivery_status']) self.message_with_status = u"%s$%s$%s" % (self.message.key, timestamp, status) return super(Event, self).save()
class InboundMessageVNone(Model): bucket = 'inboundmessage' # key is message_id msg = VumiMessage(TransportUserMessage) batch = ForeignKey(BatchVNone, null=True)
class EventVNone(Model): bucket = 'event' # key is event_id event = VumiMessage(TransportEvent) message = ForeignKey(OutboundMessageVNone)
class ForeignKeyModel(Model): simple = ForeignKey(SimpleModel, null=True)
class Event(Model): # key is message_id event = VumiMessage(TransportEvent) message = ForeignKey(OutboundMessage)
class Conversation(Model): """A conversation with an audience""" VERSION = 3 MIGRATOR = ConversationMigrator user_account = ForeignKey(UserAccount) name = Unicode(max_length=255) description = Unicode(default=u'') conversation_type = Unicode(index=True) config = Json(default=dict) extra_endpoints = ListOf(Unicode()) created_at = Timestamp(default=datetime.utcnow, index=True) archived_at = Timestamp(null=True, index=True) archive_status = Unicode(default=CONVERSATION_ACTIVE, index=True) status = Unicode(default=CONVERSATION_STOPPED, index=True) groups = ManyToMany(ContactGroup) batch = ForeignKey(Batch) delivery_class = Unicode(null=True) def active(self): return self.archive_status == CONVERSATION_ACTIVE def archived(self): return self.archive_status == CONVERSATION_ARCHIVED def ended(self): # TODO: Get rid of this once the old UI finally goes away. return self.archived() def starting(self): return self.status == CONVERSATION_STARTING def running(self): return self.status == CONVERSATION_RUNNING def stopping(self): return self.status == CONVERSATION_STOPPING def stopped(self): return self.status == CONVERSATION_STOPPED def is_draft(self): # TODO: Get rid of this once the old UI finally goes away. return self.active() and self.status == CONVERSATION_STOPPED def get_status(self): """Get the status of this conversation. Possible values are: * CONVERSATION_STARTING * CONVERSATION_RUNNING * CONVERSATION_STOPPING * CONVERSATION_STOPPED :rtype: str """ return self.status # The following are to keep the implementation of this stuff in the model # rather than potentially multiple external places. def set_status_starting(self): self.status = CONVERSATION_STARTING def set_status_started(self): self.status = CONVERSATION_RUNNING def set_status_stopping(self): self.status = CONVERSATION_STOPPING def set_status_stopped(self): self.status = CONVERSATION_STOPPED def set_status_finished(self): self.archive_status = CONVERSATION_ARCHIVED def add_group(self, group): if isinstance(group, ContactGroup): self.groups.add(group) else: self.groups.add_key(group) def __unicode__(self): return self.name def get_contacts_addresses(self, contacts): """ Get the contacts assigned to this group with an address attribute that is appropriate for this conversation's delivery_class """ addrs = [contact.addr_for(self.delivery_class) for contact in contacts] return [addr for addr in addrs if addr] def get_connector(self): return GoConnector.for_conversation(self.conversation_type, self.key)
class ConversationV1(Model): """A conversation with an audience""" VERSION = 1 MIGRATOR = ConversationMigrator bucket = 'conversation' user_account = ForeignKey(UserAccount) name = Unicode(max_length=255) description = Unicode(default=u'') conversation_type = Unicode(index=True) config = Json(default=dict) created_at = Timestamp(default=datetime.utcnow, index=True) start_timestamp = Timestamp(index=True) end_timestamp = Timestamp(null=True, index=True) status = Unicode(default=CONVERSATION_DRAFT, index=True) groups = ManyToMany(ContactGroup) batches = ManyToMany(Batch) delivery_class = Unicode(null=True) delivery_tag_pool = Unicode(null=True) delivery_tag = Unicode(null=True) def started(self): return self.running() or self.ended() def ended(self): return self.status == CONVERSATION_FINISHED def running(self): return self.status == CONVERSATION_RUNNING def get_status(self): """ Get the status of this conversation :rtype: str, (CONVERSATION_FINISHED, CONVERSATION_RUNNING, or CONVERSATION_DRAFT) """ return self.status # The following are to keep the implementation of this stuff in the model # rather than potentially multiple external places. def set_status_started(self): self.status = CONVERSATION_RUNNING def set_status_finished(self): self.status = CONVERSATION_FINISHED def add_group(self, group): if isinstance(group, ContactGroup): self.groups.add(group) else: self.groups.add_key(group) def __unicode__(self): return self.name def get_contacts_addresses(self, contacts): """ Get the contacts assigned to this group with an address attribute that is appropriate for this conversation's delivery_class """ addrs = [contact.addr_for(self.delivery_class) for contact in contacts] return [addr for addr in addrs if addr] def get_routing_name(self): return ':'.join((self.conversation_type, self.key))
class ConversationVNone(Model): """A conversation with an audience""" MIGRATOR = ConversationMigrator bucket = 'conversation' user_account = ForeignKey(UserAccount) subject = Unicode(max_length=255) message = Unicode() start_timestamp = Timestamp() end_timestamp = Timestamp(null=True, index=True) created_at = Timestamp(default=datetime.utcnow) groups = ManyToMany(ContactGroup) conversation_type = Unicode() delivery_class = Unicode(null=True) delivery_tag_pool = Unicode(null=True) delivery_tag = Unicode(null=True) batches = ManyToMany(Batch) metadata = Json(null=True) def started(self): # TODO: Better way to tell if we've started than looking for batches. return bool(self.batches.keys()) def ended(self): return self.end_timestamp is not None def running(self): return self.started() and not self.ended() def get_status(self): """ Get the status of this conversation :rtype: str, (CONVERSATION_FINISHED, CONVERSATION_RUNNING, or CONVERSATION_DRAFT) """ if self.ended(): return CONVERSATION_FINISHED elif self.running(): return CONVERSATION_RUNNING else: return CONVERSATION_DRAFT def add_group(self, group): if isinstance(group, ContactGroup): self.groups.add(group) else: self.groups.add_key(group) def __unicode__(self): return self.subject def get_contacts_addresses(self, contacts): """ Get the contacts assigned to this group with an address attribute that is appropriate for this conversation's delivery_class """ addrs = [contact.addr_for(self.delivery_class) for contact in contacts] return [addr for addr in addrs if addr]
class OptOut(Model): """An opt_out""" user_account = ForeignKey(UserAccount) message = Unicode(null=True) created_at = Timestamp(default=datetime.utcnow)
class ForeignKeyModel(Model): """ Toy model for ForeignKey tests. """ referenced = ForeignKey(ReferencedModel, null=True)
class Router(Model): """A router for sending messages to interesting places.""" VERSION = 1 MIGRATOR = None user_account = ForeignKey(UserAccount) name = Unicode(max_length=255) description = Unicode(default=u'') router_type = Unicode(index=True) config = Json(default=dict) extra_inbound_endpoints = ListOf(Unicode()) extra_outbound_endpoints = ListOf(Unicode()) created_at = Timestamp(default=datetime.utcnow, index=True) archived_at = Timestamp(null=True, index=True) archive_status = Unicode(default=ROUTER_ACTIVE, index=True) status = Unicode(default=ROUTER_STOPPED, index=True) batch = ForeignKey(Batch) def active(self): return self.archive_status == ROUTER_ACTIVE def archived(self): return self.archive_status == ROUTER_ARCHIVED def starting(self): return self.status == ROUTER_STARTING def running(self): return self.status == ROUTER_RUNNING def stopping(self): return self.status == ROUTER_STOPPING def stopped(self): return self.status == ROUTER_STOPPED # The following are to keep the implementation of this stuff in the model # rather than potentially multiple external places. def set_status_starting(self): self.status = ROUTER_STARTING def set_status_started(self): self.status = ROUTER_RUNNING def set_status_stopping(self): self.status = ROUTER_STOPPING def set_status_stopped(self): self.status = ROUTER_STOPPED def set_status_finished(self): self.archive_status = ROUTER_ARCHIVED def __unicode__(self): return self.name def get_inbound_connector(self): return GoConnector.for_router(self.router_type, self.key, GoConnector.INBOUND) def get_outbound_connector(self): return GoConnector.for_router(self.router_type, self.key, GoConnector.OUTBOUND)