|
|
| (2 intermediate revisions by 2 users not shown) |
| Line 47: |
Line 47: |
| If a user registers, we should look for any anonymous watches with that email address and add them to the user, when the user is confirmed. | | If a user registers, we should look for any anonymous watches with that email address and add them to the user, when the user is confirmed. |
|
| |
|
| = Evolution of table structure = | | = Schedule = |
| | A strawman schedule—when to target having things up for review: |
| | * 1/24: fire() offthreading (up for review) |
| | * 1/26: object_id moving; de-duplication |
| | * 1/28: templating; confirmation views |
| | * 2/1: claiming; porting old client code & deleting old subsystem code (which go together) |
| | * 2/4: migrating old watches |
|
| |
|
| == Iteration 1: Event type determines the format of the path column(s). ==
| | That should be possible with 2 people on it; we have between 3 and 4. Have fun! |
| path path2* type** email/whatever
| |
| 15 DocumentEvent whatever@whatever.com
| |
| 15.en-US DocumentEvent
| |
| 15.de.2837 DocumentEvent
| |
| 15.en-US.30 DocumentSignificanceEvent
| |
| TagEvent
| |
|
| |
| * optional. Choose the split per event type based on where orthogonality happens.
| |
| ** An event type, which can span, frex, use cases 1-3 below. Determines the use of the spec fields.
| |
|
| |
|
| | | [[Category:Support Archive]] |
| == Iteration 2: Get ref-integ on content type. ==
| |
| content_type object_id path event_type email/? (usecase)
| |
| 15 en-US DocumentLocaleEvent 2
| |
| 15 DefaultEvent 1
| |
| 15 2837 DefaultEvent 3
| |
| 15 en-US.30 DocumentLocaleSignifEvent 7
| |
| 15 de.L.A UntranslatedEvent 5
| |
| | |
| Don't make path col unicode: save space.
| |
| | |
| | |
| == Iteration 3: More normalization, yielding better queryability: orthogonality of path elements (not just hierarchy) is possible without ridiculously inefficient substring queries. I don't think the joins would be too bad, but benches would be nice. Maintains ref integ of content type and object. ==
| |
| watches:
| |
| id content_type object_id event_type user/email/whatever
| |
| 1 15 2345 UntranslatedEvent
| |
| | |
| watch_elements:
| |
| watch_id name value (int for size/speed/ad hoc joins)
| |
| 1 lang CRC32(de) # Postgres doesn't have a CRC32 function, so ad hoc joins on string keys would be a pain.
| |
| 1 localizable 1
| |
| 1 approved 1
| |
| | |
| on UntranslatedEvent for object_id=2345 and content_type=15:
| |
| select distinct email from watches
| |
| left join watch_elements lang on watches.id=lang.watch_id and name='LANG' and (value IS NULL or value='de')
| |
| left join watch_elements localizable on watches.id=localizable.watch_id and name='LOCA' and (value IS NULL or value='1')
| |
| left join watch_elements approved on watches.id=approved.watch_id and name='APPR' and (value IS NULL or value='1')
| |
| where content_type=15 and (object_id=2345 or object_id IS NULL) and event_type=UntranslatedEvent
| |
| # It's an (n+1)-table join where n is the max length of the path (excluding content type and object ID). n of the joins, however, would be selecting among the same n rows: not a big deal.
| |
| | |
| | |
| == Iteration 4: Move content_type and object_id into watch_elements? See what the benches say. ==
| |
| watches:
| |
| id event_type user/email/whatever
| |
| 1 UntranslatedEvent
| |
|
| |
| watch_elements:
| |
| watch_id name value (int for size/speed/ad hoc joins)
| |
| 1 lang CRC32(de) # Postgres doesn't support CRC32 in the DB, so ad hoc joins on string keys would be a drag.
| |
| 1 localizable 1
| |
| 1 approved 1
| |
| 1 content_type 15
| |
| 1 object_id 2345
| |
| | |
| This will bring it up to an (n+1)-table join, n being the max length of a certain event type's path *including* content type and object ID.
| |
| | |
| : Don't hamper work now because of Postgres. Assume we're on MySQL until Oracle shows up with a bill.
| |
| | |
| = Features =
| |
| * Maybe blocking an addy someday till a mail is confirmed (to prevent spamming)?
| |
| * Daily digests, supported by 2 tables:
| |
| digests:
| |
| user event_type
| |
|
| |
| digest_elements (values plugged into the template specified by event_type):
| |
| name value
| |
| id 8634
| |
| hat_color red
| |
| | |
| = API Sketch =
| |
| class ObjectModifiedWatchType(object):
| |
| """A notification which fires on the modification of a specific object"""
| |
|
| |
| code = 'modi' # Key for the event_type column. Should be same size as a 32-bit int (but more memorable) if we store as a binary char(4). Migrations will be easy in case of collisions. All-lowercase codes are reserved for the notification app itself.
| |
|
| |
| @classmethod
| |
| def fire(cls, obj):
| |
| """Signal listener which sends any appropriate notifications"""
| |
|
| |
| @classmethod
| |
| def create(cls, object):
| |
| """Create (and save, and return) a watch which fires on the modification of the given object."""
| |
|
| |
| ...
| |
|
| |
| objectModifiedSignal.register(ObjectCreatedEvent.listener) # modulo spelling
| |
|
| |
|
| |
| class ContentTypeModifiedWatchType(...)
| |
| code = 'modi' # Can share a key with the above since the NULL in the object_id column and the non-NULL in the content_type_id column are sufficient to distinguish them.
| |
|
| |
| class ContentTypeCreatedWatchType(...)
| |
| code = 'crea'
| |
|
| |
| class LocaleCreationWatchType(...): # abstract because it doesn't provide a template. Might not really be worth making this abstract. We don't have a lot of locale-having things atm.
| |
| """A notification which fires when an object (like a wiki Document) is created with a given locale.
| |
|
| |
| Works with anything that has a `locale` attr.
| |
|
| |
| """
| |
| code = 'WLCr' # convention: capital letters start a new word
| |
|
| |
| @classmethod
| |
| def create(cls, locale, content_type=None):
| |
| """Watch type for the creation of any object of a given locale.
| |
|
| |
| Specify a content_type to limit to a particular type.
| |
|
| |
| """
| |
| watch = Watches.create(content_type=content_type, object_id=None, event_type=cls.code, user=[figure out how to represent this relationship to support anonymous also])
| |
|
| |
| # WatchElements implements a key/value store. We might use 4-char codes as the keys, again because they're more memorable than ints and because they have to be unique only within an event_type. Thus, none are reserved.
| |
| WatchElements.create(watch=watch, name='locl', value=crc32(locale))
| |
|
| |
| @classmethod
| |
| def fire(cls, obj):
| |
| # TODO: Can probably use the ORM for this:
| |
| watches = execute("""select distinct user (or whatever) from watches
| |
| inner join watch_elements locale on watches.id=lang.watch_id and name='locl' and value={obj.locale}
| |
| where content_type IS NULL or content_type={obj.content_type}
| |
| AND event_type={cls.code}""")
| |
| for w in watches:
| |
| cls.notify(obj) # builds and sends one mail, for example
| |
|
| |
| @classmethod
| |
| def notify(cls, obj):
| |
| # Send a mail or what have you.
| |
| raise NotImplementError
| |
|
| |
| class WikiLocaleCreationWatchType(LocaleCreationWatchType):
| |
| @classmethod
| |
| def notify(cls, obj):
| |
| send_mail_in_a_task(some_template.render(obj.this, obj.that, ...))
| |
|
| |
| objectCreatedSignal.register(WikiLocaleCreationWatch.fire)
| |