From 15d3c7bc880120b4ec4dae36d0b82c614aeef276 Mon Sep 17 00:00:00 2001
From: Branson Stephens <branson.stephens@ligo.org>
Date: Fri, 19 Jun 2015 08:48:44 -0500
Subject: [PATCH] Created SimInspiralEvent subclass and migration, as well as
 code to ingest the SimInspiral table and a new template for
 injection-specific attributes.

---
 gracedb/alert.py                              |   7 +-
 .../0044_auto__add_siminspiralevent.py        | 441 ++++++++++++++++++
 gracedb/models.py                             |  70 +++
 gracedb/query.py                              |   1 +
 gracedb/translator.py                         | 115 +++--
 gracedb/view_logic.py                         |  41 +-
 gracedb/view_utils.py                         |  64 +++
 gracedb/views.py                              |   2 +
 ...8_add_siminspiral_event_view_permission.py |  56 +++
 templates/gracedb/event_detail_injection.html |  86 ++++
 10 files changed, 821 insertions(+), 62 deletions(-)
 create mode 100644 gracedb/migrations/0044_auto__add_siminspiralevent.py
 create mode 100644 migrations/auth/0008_add_siminspiral_event_view_permission.py
 create mode 100644 templates/gracedb/event_detail_injection.html

diff --git a/gracedb/alert.py b/gracedb/alert.py
index ecb75f956..623504c61 100644
--- a/gracedb/alert.py
+++ b/gracedb/alert.py
@@ -50,7 +50,10 @@ def issueAlertForLabel(event, label, doxmpp, serialized_event=None):
         for recip in trigger.contacts.all():
             profileRecips.append(recip.email)
 
-    subject = "[gracedb] %s / %s / %s / %s" % (label.name, event.pipeline.name, event.search.name, event.graceid())
+    if event.search:
+        subject = "[gracedb] %s / %s / %s / %s" % (label.name, event.pipeline.name, event.search.name, event.graceid())
+    else:
+        subject = "[gracedb] %s / %s / %s" % (label.name, event.pipeline.name, event.graceid())
 
     message = "A %s event with graceid %s was labelled with %s" % \
               (event.pipeline.name, event.graceid(), label.name)
@@ -75,7 +78,7 @@ def issueAlertForLabel(event, label, doxmpp, serialized_event=None):
 def issueEmailAlert(event, event_url):
 
     # XXX FIXME
-    if event.search.name == 'MDC':
+    if event.search and event.search.name == 'MDC':
         return
 
     # Gather Recipients
diff --git a/gracedb/migrations/0044_auto__add_siminspiralevent.py b/gracedb/migrations/0044_auto__add_siminspiralevent.py
new file mode 100644
index 000000000..39bcf0f46
--- /dev/null
+++ b/gracedb/migrations/0044_auto__add_siminspiralevent.py
@@ -0,0 +1,441 @@
+# -*- coding: utf-8 -*-
+from south.utils import datetime_utils as datetime
+from south.db import db
+from south.v2 import SchemaMigration
+from django.db import models
+
+
+class Migration(SchemaMigration):
+
+    def forwards(self, orm):
+        # Adding model 'SimInspiralEvent'
+        db.create_table(u'gracedb_siminspiralevent', (
+            (u'event_ptr', self.gf('django.db.models.fields.related.OneToOneField')(to=orm['gracedb.Event'], unique=True, primary_key=True)),
+            ('mass1', self.gf('django.db.models.fields.FloatField')(null=True)),
+            ('mass2', self.gf('django.db.models.fields.FloatField')(null=True)),
+            ('eta', self.gf('django.db.models.fields.FloatField')(null=True)),
+            ('amp_order', self.gf('django.db.models.fields.IntegerField')(null=True)),
+            ('coa_phase', self.gf('django.db.models.fields.FloatField')(null=True)),
+            ('mchirp', self.gf('django.db.models.fields.FloatField')(null=True)),
+            ('spin1y', self.gf('django.db.models.fields.FloatField')(null=True)),
+            ('spin1x', self.gf('django.db.models.fields.FloatField')(null=True)),
+            ('spin1z', self.gf('django.db.models.fields.FloatField')(null=True)),
+            ('spin2x', self.gf('django.db.models.fields.FloatField')(null=True)),
+            ('spin2y', self.gf('django.db.models.fields.FloatField')(null=True)),
+            ('spin2z', self.gf('django.db.models.fields.FloatField')(null=True)),
+            ('geocent_end_time', self.gf('django.db.models.fields.IntegerField')(null=True)),
+            ('geocent_end_time_ns', self.gf('django.db.models.fields.IntegerField')(null=True)),
+            ('end_time_gmst', self.gf('django.db.models.fields.FloatField')(null=True)),
+            ('f_lower', self.gf('django.db.models.fields.FloatField')(null=True)),
+            ('f_final', self.gf('django.db.models.fields.FloatField')(null=True)),
+            ('distance', self.gf('django.db.models.fields.FloatField')(null=True)),
+            ('latitude', self.gf('django.db.models.fields.FloatField')(null=True)),
+            ('longitude', self.gf('django.db.models.fields.FloatField')(null=True)),
+            ('polarization', self.gf('django.db.models.fields.FloatField')(null=True)),
+            ('inclination', self.gf('django.db.models.fields.FloatField')(null=True)),
+            ('theta0', self.gf('django.db.models.fields.FloatField')(null=True)),
+            ('phi0', self.gf('django.db.models.fields.FloatField')(null=True)),
+            ('waveform', self.gf('django.db.models.fields.CharField')(default='', max_length=50, blank=True)),
+            ('numrel_mode_min', self.gf('django.db.models.fields.IntegerField')(null=True)),
+            ('numrel_mode_max', self.gf('django.db.models.fields.IntegerField')(null=True)),
+            ('numrel_data', self.gf('django.db.models.fields.CharField')(default='', max_length=50, blank=True)),
+            ('source', self.gf('django.db.models.fields.CharField')(default='', max_length=50, blank=True)),
+            ('taper', self.gf('django.db.models.fields.CharField')(default='', max_length=50, blank=True)),
+            ('bandpass', self.gf('django.db.models.fields.IntegerField')(null=True)),
+            ('alpha', self.gf('django.db.models.fields.FloatField')(null=True)),
+            ('beta', self.gf('django.db.models.fields.FloatField')(null=True)),
+            ('psi0', self.gf('django.db.models.fields.FloatField')(null=True)),
+            ('psi3', self.gf('django.db.models.fields.FloatField')(null=True)),
+            ('alpha1', self.gf('django.db.models.fields.FloatField')(null=True)),
+            ('alpha2', self.gf('django.db.models.fields.FloatField')(null=True)),
+            ('alpha3', self.gf('django.db.models.fields.FloatField')(null=True)),
+            ('alpha4', self.gf('django.db.models.fields.FloatField')(null=True)),
+            ('alpha5', self.gf('django.db.models.fields.FloatField')(null=True)),
+            ('alpha6', self.gf('django.db.models.fields.FloatField')(null=True)),
+            ('g_end_time', self.gf('django.db.models.fields.IntegerField')(null=True)),
+            ('g_end_time_ns', self.gf('django.db.models.fields.IntegerField')(null=True)),
+            ('h_end_time', self.gf('django.db.models.fields.IntegerField')(null=True)),
+            ('h_end_time_ns', self.gf('django.db.models.fields.IntegerField')(null=True)),
+            ('l_end_time', self.gf('django.db.models.fields.IntegerField')(null=True)),
+            ('l_end_time_ns', self.gf('django.db.models.fields.IntegerField')(null=True)),
+            ('t_end_time', self.gf('django.db.models.fields.IntegerField')(null=True)),
+            ('t_end_time_ns', self.gf('django.db.models.fields.IntegerField')(null=True)),
+            ('v_end_time', self.gf('django.db.models.fields.IntegerField')(null=True)),
+            ('v_end_time_ns', self.gf('django.db.models.fields.IntegerField')(null=True)),
+            ('eff_dist_g', self.gf('django.db.models.fields.FloatField')(null=True)),
+            ('eff_dist_h', self.gf('django.db.models.fields.FloatField')(null=True)),
+            ('eff_dist_l', self.gf('django.db.models.fields.FloatField')(null=True)),
+            ('eff_dist_t', self.gf('django.db.models.fields.FloatField')(null=True)),
+            ('eff_dist_v', self.gf('django.db.models.fields.FloatField')(null=True)),
+            ('source_channel', self.gf('django.db.models.fields.CharField')(default='', max_length=50, blank=True)),
+            ('destination_channel', self.gf('django.db.models.fields.CharField')(default='', max_length=50, blank=True)),
+        ))
+        db.send_create_signal(u'gracedb', ['SimInspiralEvent'])
+
+
+    def backwards(self, orm):
+        # Deleting model 'SimInspiralEvent'
+        db.delete_table(u'gracedb_siminspiralevent')
+
+
+    models = {
+        u'auth.group': {
+            'Meta': {'object_name': 'Group'},
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
+            'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
+        },
+        u'auth.permission': {
+            'Meta': {'ordering': "(u'content_type__app_label', u'content_type__model', u'codename')", 'unique_together': "((u'content_type', u'codename'),)", 'object_name': 'Permission'},
+            'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['contenttypes.ContentType']"}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
+        },
+        u'auth.user': {
+            'Meta': {'object_name': 'User'},
+            'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+            'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
+            'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
+            'groups': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "u'user_set'", 'blank': 'True', 'to': u"orm['auth.Group']"}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+            'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+            'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
+            'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+            'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "u'user_set'", 'blank': 'True', 'to': u"orm['auth.Permission']"}),
+            'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
+        },
+        u'contenttypes.contenttype': {
+            'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
+            'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
+        },
+        u'gracedb.approval': {
+            'Meta': {'object_name': 'Approval'},
+            'approvedEvent': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['gracedb.Event']"}),
+            'approver': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['auth.User']"}),
+            'approvingCollaboration': ('django.db.models.fields.CharField', [], {'max_length': '1'}),
+            'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'})
+        },
+        u'gracedb.coincinspiralevent': {
+            'Meta': {'ordering': "['-id']", 'object_name': 'CoincInspiralEvent', '_ormbases': [u'gracedb.Event']},
+            'combined_far': ('django.db.models.fields.FloatField', [], {'null': 'True'}),
+            'end_time': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True'}),
+            'end_time_ns': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True'}),
+            u'event_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': u"orm['gracedb.Event']", 'unique': 'True', 'primary_key': 'True'}),
+            'false_alarm_rate': ('django.db.models.fields.FloatField', [], {'null': 'True'}),
+            'ifos': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '20'}),
+            'mass': ('django.db.models.fields.FloatField', [], {'null': 'True'}),
+            'mchirp': ('django.db.models.fields.FloatField', [], {'null': 'True'}),
+            'minimum_duration': ('django.db.models.fields.FloatField', [], {'null': 'True'}),
+            'snr': ('django.db.models.fields.FloatField', [], {'null': 'True'})
+        },
+        u'gracedb.embbeventlog': {
+            'Meta': {'ordering': "['-created', '-N']", 'unique_together': "(('event', 'N'),)", 'object_name': 'EMBBEventLog'},
+            'N': ('django.db.models.fields.IntegerField', [], {}),
+            'comment': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+            'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
+            'dec': ('django.db.models.fields.FloatField', [], {'null': 'True'}),
+            'decList': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+            'decWidth': ('django.db.models.fields.FloatField', [], {'null': 'True'}),
+            'decWidthList': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+            'duration': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True'}),
+            'durationList': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+            'eel_status': ('django.db.models.fields.CharField', [], {'max_length': '2'}),
+            'event': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['gracedb.Event']"}),
+            'extra_info_dict': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+            'footprintID': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+            'gpstime': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True'}),
+            'gpstimeList': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+            'group': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['gracedb.EMGroup']"}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'instrument': ('django.db.models.fields.CharField', [], {'max_length': '200', 'blank': 'True'}),
+            'obs_status': ('django.db.models.fields.CharField', [], {'max_length': '2'}),
+            'ra': ('django.db.models.fields.FloatField', [], {'null': 'True'}),
+            'raList': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+            'raWidth': ('django.db.models.fields.FloatField', [], {'null': 'True'}),
+            'raWidthList': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+            'submitter': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['auth.User']"}),
+            'waveband': ('django.db.models.fields.CharField', [], {'max_length': '25'})
+        },
+        u'gracedb.emfootprint': {
+            'Meta': {'ordering': "['-N']", 'unique_together': "(('observation', 'N'),)", 'object_name': 'EMFootprint'},
+            'N': ('django.db.models.fields.IntegerField', [], {}),
+            'dec': ('django.db.models.fields.FloatField', [], {}),
+            'decWidth': ('django.db.models.fields.FloatField', [], {}),
+            'exposure_time': ('django.db.models.fields.PositiveIntegerField', [], {}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'observation': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['gracedb.EMObservation']"}),
+            'ra': ('django.db.models.fields.FloatField', [], {}),
+            'raWidth': ('django.db.models.fields.FloatField', [], {}),
+            'start_time': ('django.db.models.fields.DateTimeField', [], {})
+        },
+        u'gracedb.emgroup': {
+            'Meta': {'object_name': 'EMGroup'},
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '20'})
+        },
+        u'gracedb.emobservation': {
+            'Meta': {'ordering': "['-created', '-N']", 'unique_together': "(('event', 'N'),)", 'object_name': 'EMObservation'},
+            'N': ('django.db.models.fields.IntegerField', [], {}),
+            'comment': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+            'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
+            'dec': ('django.db.models.fields.FloatField', [], {'null': 'True'}),
+            'decWidth': ('django.db.models.fields.FloatField', [], {'null': 'True'}),
+            'event': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['gracedb.Event']"}),
+            'group': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['gracedb.EMGroup']"}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'ra': ('django.db.models.fields.FloatField', [], {'null': 'True'}),
+            'raWidth': ('django.db.models.fields.FloatField', [], {'null': 'True'}),
+            'submitter': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['auth.User']"})
+        },
+        u'gracedb.event': {
+            'Meta': {'ordering': "['-id']", 'object_name': 'Event'},
+            'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
+            'far': ('django.db.models.fields.FloatField', [], {'null': 'True'}),
+            'gpstime': ('django.db.models.fields.DecimalField', [], {'null': 'True', 'max_digits': '16', 'decimal_places': '6'}),
+            'group': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['gracedb.Group']"}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'instruments': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '20'}),
+            'labels': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['gracedb.Label']", 'through': u"orm['gracedb.Labelling']", 'symmetrical': 'False'}),
+            'likelihood': ('django.db.models.fields.FloatField', [], {'null': 'True'}),
+            'nevents': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True'}),
+            'perms': ('django.db.models.fields.TextField', [], {'null': 'True'}),
+            'pipeline': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['gracedb.Pipeline']"}),
+            'search': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['gracedb.Search']", 'null': 'True'}),
+            'submitter': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['auth.User']"})
+        },
+        u'gracedb.eventlog': {
+            'Meta': {'ordering': "['-created', '-N']", 'unique_together': "(('event', 'N'),)", 'object_name': 'EventLog'},
+            'N': ('django.db.models.fields.IntegerField', [], {}),
+            'comment': ('django.db.models.fields.TextField', [], {}),
+            'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
+            'event': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['gracedb.Event']"}),
+            'file_version': ('django.db.models.fields.IntegerField', [], {'null': 'True'}),
+            'filename': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '100'}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'issuer': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['auth.User']"})
+        },
+        u'gracedb.grbevent': {
+            'Meta': {'ordering': "['-id']", 'object_name': 'GrbEvent', '_ormbases': [u'gracedb.Event']},
+            'author_ivorn': ('django.db.models.fields.CharField', [], {'max_length': '200', 'null': 'True'}),
+            'author_shortname': ('django.db.models.fields.CharField', [], {'max_length': '200', 'null': 'True'}),
+            'coord_system': ('django.db.models.fields.CharField', [], {'max_length': '200', 'null': 'True'}),
+            'dec': ('django.db.models.fields.FloatField', [], {'null': 'True'}),
+            'error_radius': ('django.db.models.fields.FloatField', [], {'null': 'True'}),
+            u'event_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': u"orm['gracedb.Event']", 'unique': 'True', 'primary_key': 'True'}),
+            'how_description': ('django.db.models.fields.CharField', [], {'max_length': '200', 'null': 'True'}),
+            'how_reference_url': ('django.db.models.fields.URLField', [], {'max_length': '200', 'null': 'True'}),
+            'ivorn': ('django.db.models.fields.CharField', [], {'max_length': '200', 'null': 'True'}),
+            'observatory_location_id': ('django.db.models.fields.CharField', [], {'max_length': '200', 'null': 'True'}),
+            'ra': ('django.db.models.fields.FloatField', [], {'null': 'True'}),
+            't90': ('django.db.models.fields.FloatField', [], {'null': 'True'}),
+            'trigger_duration': ('django.db.models.fields.FloatField', [], {'null': 'True'})
+        },
+        u'gracedb.group': {
+            'Meta': {'object_name': 'Group'},
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '20'})
+        },
+        u'gracedb.label': {
+            'Meta': {'object_name': 'Label'},
+            'defaultColor': ('django.db.models.fields.CharField', [], {'default': "'black'", 'max_length': '20'}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '20'})
+        },
+        u'gracedb.labelling': {
+            'Meta': {'object_name': 'Labelling'},
+            'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
+            'creator': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['auth.User']"}),
+            'event': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['gracedb.Event']"}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'label': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['gracedb.Label']"})
+        },
+        u'gracedb.multiburstevent': {
+            'Meta': {'ordering': "['-id']", 'object_name': 'MultiBurstEvent', '_ormbases': [u'gracedb.Event']},
+            'amplitude': ('django.db.models.fields.FloatField', [], {'null': 'True'}),
+            'bandwidth': ('django.db.models.fields.FloatField', [], {'null': 'True'}),
+            'central_freq': ('django.db.models.fields.FloatField', [], {'null': 'True'}),
+            'confidence': ('django.db.models.fields.FloatField', [], {'null': 'True'}),
+            'duration': ('django.db.models.fields.FloatField', [], {'null': 'True'}),
+            u'event_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': u"orm['gracedb.Event']", 'unique': 'True', 'primary_key': 'True'}),
+            'false_alarm_rate': ('django.db.models.fields.FloatField', [], {'null': 'True'}),
+            'ifos': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '20'}),
+            'ligo_angle': ('django.db.models.fields.FloatField', [], {'null': 'True'}),
+            'ligo_angle_sig': ('django.db.models.fields.FloatField', [], {'null': 'True'}),
+            'ligo_axis_dec': ('django.db.models.fields.FloatField', [], {'null': 'True'}),
+            'ligo_axis_ra': ('django.db.models.fields.FloatField', [], {'null': 'True'}),
+            'peak_time': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True'}),
+            'peak_time_ns': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True'}),
+            'snr': ('django.db.models.fields.FloatField', [], {'null': 'True'}),
+            'start_time': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True'}),
+            'start_time_ns': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True'})
+        },
+        u'gracedb.pipeline': {
+            'Meta': {'object_name': 'Pipeline'},
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
+        },
+        u'gracedb.search': {
+            'Meta': {'object_name': 'Search'},
+            'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
+        },
+        u'gracedb.siminspiralevent': {
+            'Meta': {'ordering': "['-id']", 'object_name': 'SimInspiralEvent', '_ormbases': [u'gracedb.Event']},
+            'alpha': ('django.db.models.fields.FloatField', [], {'null': 'True'}),
+            'alpha1': ('django.db.models.fields.FloatField', [], {'null': 'True'}),
+            'alpha2': ('django.db.models.fields.FloatField', [], {'null': 'True'}),
+            'alpha3': ('django.db.models.fields.FloatField', [], {'null': 'True'}),
+            'alpha4': ('django.db.models.fields.FloatField', [], {'null': 'True'}),
+            'alpha5': ('django.db.models.fields.FloatField', [], {'null': 'True'}),
+            'alpha6': ('django.db.models.fields.FloatField', [], {'null': 'True'}),
+            'amp_order': ('django.db.models.fields.IntegerField', [], {'null': 'True'}),
+            'bandpass': ('django.db.models.fields.IntegerField', [], {'null': 'True'}),
+            'beta': ('django.db.models.fields.FloatField', [], {'null': 'True'}),
+            'coa_phase': ('django.db.models.fields.FloatField', [], {'null': 'True'}),
+            'destination_channel': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '50', 'blank': 'True'}),
+            'distance': ('django.db.models.fields.FloatField', [], {'null': 'True'}),
+            'eff_dist_g': ('django.db.models.fields.FloatField', [], {'null': 'True'}),
+            'eff_dist_h': ('django.db.models.fields.FloatField', [], {'null': 'True'}),
+            'eff_dist_l': ('django.db.models.fields.FloatField', [], {'null': 'True'}),
+            'eff_dist_t': ('django.db.models.fields.FloatField', [], {'null': 'True'}),
+            'eff_dist_v': ('django.db.models.fields.FloatField', [], {'null': 'True'}),
+            'end_time_gmst': ('django.db.models.fields.FloatField', [], {'null': 'True'}),
+            'eta': ('django.db.models.fields.FloatField', [], {'null': 'True'}),
+            u'event_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': u"orm['gracedb.Event']", 'unique': 'True', 'primary_key': 'True'}),
+            'f_final': ('django.db.models.fields.FloatField', [], {'null': 'True'}),
+            'f_lower': ('django.db.models.fields.FloatField', [], {'null': 'True'}),
+            'g_end_time': ('django.db.models.fields.IntegerField', [], {'null': 'True'}),
+            'g_end_time_ns': ('django.db.models.fields.IntegerField', [], {'null': 'True'}),
+            'geocent_end_time': ('django.db.models.fields.IntegerField', [], {'null': 'True'}),
+            'geocent_end_time_ns': ('django.db.models.fields.IntegerField', [], {'null': 'True'}),
+            'h_end_time': ('django.db.models.fields.IntegerField', [], {'null': 'True'}),
+            'h_end_time_ns': ('django.db.models.fields.IntegerField', [], {'null': 'True'}),
+            'inclination': ('django.db.models.fields.FloatField', [], {'null': 'True'}),
+            'l_end_time': ('django.db.models.fields.IntegerField', [], {'null': 'True'}),
+            'l_end_time_ns': ('django.db.models.fields.IntegerField', [], {'null': 'True'}),
+            'latitude': ('django.db.models.fields.FloatField', [], {'null': 'True'}),
+            'longitude': ('django.db.models.fields.FloatField', [], {'null': 'True'}),
+            'mass1': ('django.db.models.fields.FloatField', [], {'null': 'True'}),
+            'mass2': ('django.db.models.fields.FloatField', [], {'null': 'True'}),
+            'mchirp': ('django.db.models.fields.FloatField', [], {'null': 'True'}),
+            'numrel_data': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '50', 'blank': 'True'}),
+            'numrel_mode_max': ('django.db.models.fields.IntegerField', [], {'null': 'True'}),
+            'numrel_mode_min': ('django.db.models.fields.IntegerField', [], {'null': 'True'}),
+            'phi0': ('django.db.models.fields.FloatField', [], {'null': 'True'}),
+            'polarization': ('django.db.models.fields.FloatField', [], {'null': 'True'}),
+            'psi0': ('django.db.models.fields.FloatField', [], {'null': 'True'}),
+            'psi3': ('django.db.models.fields.FloatField', [], {'null': 'True'}),
+            'source': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '50', 'blank': 'True'}),
+            'source_channel': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '50', 'blank': 'True'}),
+            'spin1x': ('django.db.models.fields.FloatField', [], {'null': 'True'}),
+            'spin1y': ('django.db.models.fields.FloatField', [], {'null': 'True'}),
+            'spin1z': ('django.db.models.fields.FloatField', [], {'null': 'True'}),
+            'spin2x': ('django.db.models.fields.FloatField', [], {'null': 'True'}),
+            'spin2y': ('django.db.models.fields.FloatField', [], {'null': 'True'}),
+            'spin2z': ('django.db.models.fields.FloatField', [], {'null': 'True'}),
+            't_end_time': ('django.db.models.fields.IntegerField', [], {'null': 'True'}),
+            't_end_time_ns': ('django.db.models.fields.IntegerField', [], {'null': 'True'}),
+            'taper': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '50', 'blank': 'True'}),
+            'theta0': ('django.db.models.fields.FloatField', [], {'null': 'True'}),
+            'v_end_time': ('django.db.models.fields.IntegerField', [], {'null': 'True'}),
+            'v_end_time_ns': ('django.db.models.fields.IntegerField', [], {'null': 'True'}),
+            'waveform': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '50', 'blank': 'True'})
+        },
+        u'gracedb.singleinspiral': {
+            'Gamma0': ('django.db.models.fields.FloatField', [], {'null': 'True'}),
+            'Gamma1': ('django.db.models.fields.FloatField', [], {'null': 'True'}),
+            'Gamma2': ('django.db.models.fields.FloatField', [], {'null': 'True'}),
+            'Gamma3': ('django.db.models.fields.FloatField', [], {'null': 'True'}),
+            'Gamma4': ('django.db.models.fields.FloatField', [], {'null': 'True'}),
+            'Gamma5': ('django.db.models.fields.FloatField', [], {'null': 'True'}),
+            'Gamma6': ('django.db.models.fields.FloatField', [], {'null': 'True'}),
+            'Gamma7': ('django.db.models.fields.FloatField', [], {'null': 'True'}),
+            'Gamma8': ('django.db.models.fields.FloatField', [], {'null': 'True'}),
+            'Gamma9': ('django.db.models.fields.FloatField', [], {'null': 'True'}),
+            'Meta': {'object_name': 'SingleInspiral'},
+            'alpha': ('django.db.models.fields.FloatField', [], {'null': 'True'}),
+            'alpha1': ('django.db.models.fields.FloatField', [], {'null': 'True'}),
+            'alpha2': ('django.db.models.fields.FloatField', [], {'null': 'True'}),
+            'alpha3': ('django.db.models.fields.FloatField', [], {'null': 'True'}),
+            'alpha4': ('django.db.models.fields.FloatField', [], {'null': 'True'}),
+            'alpha5': ('django.db.models.fields.FloatField', [], {'null': 'True'}),
+            'alpha6': ('django.db.models.fields.FloatField', [], {'null': 'True'}),
+            'amplitude': ('django.db.models.fields.FloatField', [], {'null': 'True'}),
+            'bank_chisq': ('django.db.models.fields.FloatField', [], {'null': 'True'}),
+            'bank_chisq_dof': ('django.db.models.fields.IntegerField', [], {'null': 'True'}),
+            'beta': ('django.db.models.fields.FloatField', [], {'null': 'True'}),
+            'channel': ('django.db.models.fields.CharField', [], {'max_length': '20'}),
+            'chi': ('django.db.models.fields.FloatField', [], {'null': 'True'}),
+            'chisq': ('django.db.models.fields.FloatField', [], {'null': 'True'}),
+            'chisq_dof': ('django.db.models.fields.IntegerField', [], {'null': 'True'}),
+            'coa_phase': ('django.db.models.fields.FloatField', [], {'null': 'True'}),
+            'cont_chisq': ('django.db.models.fields.FloatField', [], {'null': 'True'}),
+            'cont_chisq_dof': ('django.db.models.fields.IntegerField', [], {'null': 'True'}),
+            'eff_distance': ('django.db.models.fields.FloatField', [], {'null': 'True'}),
+            'end_time': ('django.db.models.fields.IntegerField', [], {'null': 'True'}),
+            'end_time_gmst': ('django.db.models.fields.FloatField', [], {'null': 'True'}),
+            'end_time_ns': ('django.db.models.fields.IntegerField', [], {'null': 'True'}),
+            'eta': ('django.db.models.fields.FloatField', [], {'null': 'True'}),
+            'event': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['gracedb.Event']"}),
+            'event_duration': ('django.db.models.fields.FloatField', [], {'null': 'True'}),
+            'f_final': ('django.db.models.fields.FloatField', [], {'null': 'True'}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'ifo': ('django.db.models.fields.CharField', [], {'max_length': '20', 'null': 'True'}),
+            'impulse_time': ('django.db.models.fields.IntegerField', [], {'null': 'True'}),
+            'impulse_time_ns': ('django.db.models.fields.IntegerField', [], {'null': 'True'}),
+            'kappa': ('django.db.models.fields.FloatField', [], {'null': 'True'}),
+            'mass1': ('django.db.models.fields.FloatField', [], {'null': 'True'}),
+            'mass2': ('django.db.models.fields.FloatField', [], {'null': 'True'}),
+            'mchirp': ('django.db.models.fields.FloatField', [], {'null': 'True'}),
+            'mtotal': ('django.db.models.fields.FloatField', [], {'null': 'True'}),
+            'psi0': ('django.db.models.fields.FloatField', [], {'null': 'True'}),
+            'psi3': ('django.db.models.fields.FloatField', [], {'null': 'True'}),
+            'rsqveto_duration': ('django.db.models.fields.FloatField', [], {'null': 'True'}),
+            'search': ('django.db.models.fields.CharField', [], {'max_length': '20', 'null': 'True'}),
+            'sigmasq': ('django.db.models.fields.FloatField', [], {'null': 'True'}),
+            'snr': ('django.db.models.fields.FloatField', [], {'null': 'True'}),
+            'spin1x': ('django.db.models.fields.FloatField', [], {'null': 'True'}),
+            'spin1y': ('django.db.models.fields.FloatField', [], {'null': 'True'}),
+            'spin1z': ('django.db.models.fields.FloatField', [], {'null': 'True'}),
+            'spin2x': ('django.db.models.fields.FloatField', [], {'null': 'True'}),
+            'spin2y': ('django.db.models.fields.FloatField', [], {'null': 'True'}),
+            'spin2z': ('django.db.models.fields.FloatField', [], {'null': 'True'}),
+            'tau0': ('django.db.models.fields.FloatField', [], {'null': 'True'}),
+            'tau2': ('django.db.models.fields.FloatField', [], {'null': 'True'}),
+            'tau3': ('django.db.models.fields.FloatField', [], {'null': 'True'}),
+            'tau4': ('django.db.models.fields.FloatField', [], {'null': 'True'}),
+            'tau5': ('django.db.models.fields.FloatField', [], {'null': 'True'}),
+            'template_duration': ('django.db.models.fields.FloatField', [], {'null': 'True'}),
+            'ttotal': ('django.db.models.fields.FloatField', [], {'null': 'True'})
+        },
+        u'gracedb.tag': {
+            'Meta': {'object_name': 'Tag'},
+            'displayName': ('django.db.models.fields.CharField', [], {'max_length': '200', 'null': 'True'}),
+            'eventlogs': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['gracedb.EventLog']", 'symmetrical': 'False'}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
+        },
+        u'gracedb.voevent': {
+            'Meta': {'ordering': "['-created', '-N']", 'unique_together': "(('event', 'N'),)", 'object_name': 'VOEvent'},
+            'N': ('django.db.models.fields.IntegerField', [], {}),
+            'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
+            'event': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['gracedb.Event']"}),
+            'file_version': ('django.db.models.fields.IntegerField', [], {'null': 'True'}),
+            'filename': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '100'}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'issuer': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['auth.User']"}),
+            'ivorn': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '200'}),
+            'voevent_type': ('django.db.models.fields.CharField', [], {'max_length': '2'})
+        }
+    }
+
+    complete_apps = ['gracedb']
\ No newline at end of file
diff --git a/gracedb/models.py b/gracedb/models.py
index 178662377..8d4089ecb 100644
--- a/gracedb/models.py
+++ b/gracedb/models.py
@@ -949,6 +949,76 @@ class SingleInspiral(models.Model):
         cls._field_names = model_field_names.intersection(ligolw_field_names)
         return cls._field_names
 
+# Event subclass for injections
+class SimInspiralEvent(Event):
+    mass1                = models.FloatField(null=True)
+    mass2                = models.FloatField(null=True)
+    eta                  = models.FloatField(null=True)
+    amp_order            = models.IntegerField(null=True)
+    coa_phase            = models.FloatField(null=True)
+    mchirp               = models.FloatField(null=True)
+    spin1y               = models.FloatField(null=True)
+    spin1x               = models.FloatField(null=True)
+    spin1z               = models.FloatField(null=True)
+    spin2x               = models.FloatField(null=True)
+    spin2y               = models.FloatField(null=True)
+    spin2z               = models.FloatField(null=True)
+    geocent_end_time     = models.IntegerField(null=True)
+    geocent_end_time_ns  = models.IntegerField(null=True)
+    end_time_gmst        = models.FloatField(null=True)
+    f_lower              = models.FloatField(null=True)
+    f_final              = models.FloatField(null=True)
+    distance             = models.FloatField(null=True)
+    latitude             = models.FloatField(null=True)
+    longitude            = models.FloatField(null=True)
+    polarization         = models.FloatField(null=True)
+    inclination          = models.FloatField(null=True)
+    theta0               = models.FloatField(null=True)
+    phi0                 = models.FloatField(null=True)
+    waveform             = models.CharField(max_length=50, blank=True, default="")
+    numrel_mode_min      = models.IntegerField(null=True)
+    numrel_mode_max      = models.IntegerField(null=True)
+    numrel_data          = models.CharField(max_length=50, blank=True, default="")
+    source               = models.CharField(max_length=50, blank=True, default="")
+    taper                = models.CharField(max_length=50, blank=True, default="")
+    bandpass             = models.IntegerField(null=True)
+    alpha                = models.FloatField(null=True)
+    beta                 = models.FloatField(null=True)
+    psi0                 = models.FloatField(null=True)
+    psi3                 = models.FloatField(null=True)
+    alpha1               = models.FloatField(null=True)
+    alpha2               = models.FloatField(null=True)
+    alpha3               = models.FloatField(null=True)
+    alpha4               = models.FloatField(null=True)
+    alpha5               = models.FloatField(null=True)
+    alpha6               = models.FloatField(null=True)
+    g_end_time           = models.IntegerField(null=True)
+    g_end_time_ns        = models.IntegerField(null=True)
+    h_end_time           = models.IntegerField(null=True)
+    h_end_time_ns        = models.IntegerField(null=True)
+    l_end_time           = models.IntegerField(null=True)
+    l_end_time_ns        = models.IntegerField(null=True)
+    t_end_time           = models.IntegerField(null=True)
+    t_end_time_ns        = models.IntegerField(null=True)
+    v_end_time           = models.IntegerField(null=True)
+    v_end_time_ns        = models.IntegerField(null=True)
+    eff_dist_g           = models.FloatField(null=True)
+    eff_dist_h           = models.FloatField(null=True)
+    eff_dist_l           = models.FloatField(null=True)
+    eff_dist_t           = models.FloatField(null=True)
+    eff_dist_v           = models.FloatField(null=True)
+    # Additional desired attributes that are not in the SimInspiral table
+    source_channel       = models.CharField(max_length=50, blank=True, default="")
+    destination_channel  = models.CharField(max_length=50, blank=True, default="")
+
+    @classmethod
+    def field_names(cls):
+        try:
+            return cls._field_names
+        except AttributeError: pass
+        # We only care about the model field names in this particular case.
+        cls._field_names = [ x.name for x in cls._meta.fields ]
+        return cls._field_names
 
 ## Tags (user-defined log message attributes)
 class Tag(models.Model):
diff --git a/gracedb/query.py b/gracedb/query.py
index c86e3106d..ce643779c 100644
--- a/gracedb/query.py
+++ b/gracedb/query.py
@@ -218,6 +218,7 @@ tableTranslations = {
         'coincinspiral': 'coincinspiralevent',
         'multiburst': 'multiburstevent',
         'grb': 'grbevent',
+        'inj': 'siminspiralevent',
         }
 
 def buildDjangoQueryField(toks):
diff --git a/gracedb/translator.py b/gracedb/translator.py
index 483e881af..8916692cb 100644
--- a/gracedb/translator.py
+++ b/gracedb/translator.py
@@ -3,8 +3,9 @@ import os
 
 from .models import EventLog
 from .models import SingleInspiral
+from .models import SimInspiralEvent
 
-from glue.ligolw.utils import load_filename
+from glue.ligolw.utils import load_filename, load_fileobj
 from glue.ligolw.lsctables import CoincInspiralTable, SnglInspiralTable, use_in
 from glue.ligolw.lsctables import SimInspiralTable, MultiBurstTable, CoincTable
 from glue.ligolw.ligolw import LIGOLWContentHandler
@@ -18,6 +19,7 @@ from utils import isoToGps, isoToGpsFloat
 from utils.vfile import VersionedFile
 
 import json
+import StringIO
 
 use_in(LIGOLWContentHandler)
 
@@ -45,13 +47,15 @@ def cleanData(val, field_name, table_name='gracedb_event'):
 
 def handle_uploaded_data(event, datafilename,
                          log_filename='event.log',
-                         coinc_table_filename='coinc.xml'):
+                         coinc_table_filename='coinc.xml',
+                         file_contents=None):
 
-    log = EventLog(event=event,
-                   filename=os.path.basename(datafilename),
-                   issuer=event.submitter,
-                   comment="Original Data")
-    log.save()
+    if datafilename:
+        log = EventLog(event=event,
+                       filename=os.path.basename(datafilename),
+                       issuer=event.submitter,
+                       comment="Original Data")
+        log.save()
 
     # XXX If you can manage to get rid of the MBTA .gwf parsing and
     # the Omega event parsing, you can deprecate temp_data_loc. It 
@@ -168,52 +172,65 @@ def handle_uploaded_data(event, datafilename,
 
     elif pipeline == 'HardwareInjection':
         log_comment = "Log File Created"
-        xmldoc = load_filename(datafilename, contenthandler=LIGOLWContentHandler)
-
-        # Create Log Data
-        # XXX This is messy and redundant.  All of this is also below.
-        try:
-            log_data = ["Pipeline: %s" % pipeline]
-            origdata = SimInspiralTable.get_table(xmldoc)
-            origdata = origdata[0]
-            mchirp   = origdata.mchirp
-            mass     = (origdata.mass1, origdata.mass2)
-            spin1    = (origdata.spin1x, origdata.spin1y, origdata.spin1z)
-            spin2    = (origdata.spin2x, origdata.spin2y, origdata.spin2z)
-            end_time = (origdata.geocent_end_time, origdata.geocent_end_time_ns)
-            # XXX unused
-            #waveform = origdata.waveform
-
-            if mchirp is not None:
-                log_data.append("MChirp: %0.3f" % mchirp)
-            else:
-                log_data.append("MChirp: ---")
-            log_data.append("Component Masses: %f %f" % mass)
-            log_data.append("Component 1 Spin: (%f, %f, %f)" % spin1)
-            log_data.append("Component 2 Spin: (%f, %f, %f)" % spin2)
-            log_data.append("Geocentric End Time: %d.%d" % end_time)
-        except Exception, e:
-            log_comment = "Problem Creating Log File"
-            log_data = ["Cannot create log file", "error was:", str(e)]
-
-        log_data = "\n".join(log_data)
-
-        output_dir = os.path.dirname(datafilename)
-        write_output_files(output_dir, xmldoc, log_data,
-                           xml_fname=coinc_table_filename,
-                           log_fname=log_filename)
-
-        # Create EventLog entries about these files.
+        if datafilename:
+            xmldoc = load_filename(datafilename, contenthandler=LIGOLWContentHandler)
+        elif file_contents:
+            f = StringIO.StringIO(file_contents)
+            xmldoc, digest = load_fileobj(f, contenthandler=LIGOLWContentHandler)
+        else:
+            msg = "If you wanna make an injection event, I'm gonna need a filepath or filecontents."
+            raise ValueError(msg)
 
-        #event.gpstime = end_time[0]
+        origdata = SimInspiralTable.get_table(xmldoc)
+        origdata = origdata[0]
+        end_time = (origdata.geocent_end_time, origdata.geocent_end_time_ns)
         event.gpstime = end_time[0] + float(end_time[1])/1e9
+
+#        # Create Log Data
+#        try:
+#            log_data = ["Pipeline: %s" % pipeline]
+#            mchirp   = origdata.mchirp
+#            mass     = (origdata.mass1, origdata.mass2)
+#            spin1    = (origdata.spin1x, origdata.spin1y, origdata.spin1z)
+#            spin2    = (origdata.spin2x, origdata.spin2y, origdata.spin2z)
+#            #waveform = origdata.waveform
+#
+#            if mchirp is not None:
+#                log_data.append("MChirp: %0.3f" % mchirp)
+#            else:
+#                log_data.append("MChirp: ---")
+#            log_data.append("Component Masses: %f %f" % mass)
+#            log_data.append("Component 1 Spin: (%f, %f, %f)" % spin1)
+#            log_data.append("Component 2 Spin: (%f, %f, %f)" % spin2)
+#            log_data.append("Geocentric End Time: %d.%d" % end_time)
+#        except Exception, e:
+#            log_comment = "Problem Creating Log File"
+#            log_data = ["Cannot create log file", "error was:", str(e)]
+#        log_data = "\n".join(log_data)
+
+        # Assign attributes from the SimInspiralTable
+        field_names = SimInspiralEvent.field_names()
+        for column in field_names:
+            try:
+                value = getattr(origdata, column)
+                setattr(event, column, value)
+            except:
+                pass
         event.save()
 
-        log = EventLog(event=event,
-                       filename=log_filename,
-                       issuer=event.submitter,
-                       comment=log_comment)
-        log.save()
+        # XXX Let's not write output files for the injections. There are 
+        # simply too many of them.
+        #
+        #output_dir = os.path.dirname(datafilename)
+        #write_output_files(output_dir, xmldoc, log_data,
+        #                   xml_fname=coinc_table_filename,
+        #                   log_fname=log_filename)
+        #log = EventLog(event=event,
+        #               filename=log_filename,
+        #               issuer=event.submitter,
+        #               comment=log_comment)
+        #log.save()
+
     # XXX Submitting MBTA events by frame file is now deprecated as of 19 Dec. 2014.
     # Feel free to break this after 19 Dec. 2015.
     elif pipeline == 'MBTAOnline' and '.gwf' in datafilename:
diff --git a/gracedb/view_logic.py b/gracedb/view_logic.py
index ec5e5a973..875e2ecac 100644
--- a/gracedb/view_logic.py
+++ b/gracedb/view_logic.py
@@ -6,6 +6,7 @@ from models import Pipeline, Search
 from models import CoincInspiralEvent
 from models import MultiBurstEvent
 from models import GrbEvent
+from models import SimInspiralEvent
 from models import EMBBEventLog, EMGroup
 from models import EMObservation, EMFootprint
 from alert import issueAlert, issueAlertForLabel, issueAlertForUpdate
@@ -47,6 +48,8 @@ def _createEventFromForm(request, form):
             event = GrbEvent()
         elif pipeline.name in ['CWB', 'CWB2G']:
             event = MultiBurstEvent() 
+        elif pipeline.name in ['HardwareInjection',]:
+            event = SimInspiralEvent()
         else:
             event = Event()
 
@@ -54,15 +57,20 @@ def _createEventFromForm(request, form):
         event.group = group
         event.pipeline = pipeline
         event.search = search
+
+        # If the event is an injection, look for certain attributes in the POST data.
+        # These attributes are unfortunately not found in the SimInspiralTable
+        if pipeline.name in ['HardwareInjection',]:
+            event.source_channel = request.POST.get('source_channel', None)
+            event.destination_channel = request.POST.get('destination_channel', None)
+            event.instruments = request.POST.get('instrument', None)
+
         #  ARGH.  We don't get a graceid until we save,
         #  but we don't know in advance if we can actually
         #  create all the things we need for success!
         #  What to do?!
         event.save()
         saved = True  # in case we have to undo this.
-        # Create data directory/directories
-        #    Save uploaded file.
-
         # Create permissions objects for the new event
         assign_default_event_perms(event)
  
@@ -75,22 +83,33 @@ def _createEventFromForm(request, form):
 
         event.refresh_perms()
 
+        # Create data directory/directories
+        #    Save uploaded file.
         # Write the event data file to disk. 
-        eventDir = event.datadir()
-        os.makedirs( eventDir )
+
+        # But there are way too many hardware injections to save them to disk
+
         f = request.FILES['eventFile']
-        uploadDestination = os.path.join(eventDir, f.name)
-        fdest = VersionedFile(uploadDestination, 'w')
-        for chunk in f.chunks():
-            fdest.write(chunk)
-        fdest.close()
+        if pipeline.name not in ['HardwareInjection',]:
+            eventDir = event.datadir()
+            os.makedirs( eventDir )
+            uploadDestination = os.path.join(eventDir, f.name)
+            fdest = VersionedFile(uploadDestination, 'w')
+            for chunk in f.chunks():
+                fdest.write(chunk)
+            fdest.close()
+            file_contents = None
+        else:
+            uploadDestination = None
+            file_contents = f.read()
         # Create WIKI page
 
         # Extract Info from uploaded data
         # Temp (ha!) hack to deal with
         # out of band data from Omega to LUMIN.
         try:
-            temp_data_loc, translator_warnings  = handle_uploaded_data(event, uploadDestination)
+            temp_data_loc, translator_warnings  = handle_uploaded_data(event, uploadDestination, 
+                file_contents = file_contents)
             warnings += translator_warnings
             try:
                 # Send an alert.
diff --git a/gracedb/view_utils.py b/gracedb/view_utils.py
index 9572639cf..9d4456b5a 100644
--- a/gracedb/view_utils.py
+++ b/gracedb/view_utils.py
@@ -166,6 +166,70 @@ def eventToDict(event, columns=None, request=None):
               }
     except:
         pass
+    try:
+        # SimInspiralEvent
+        rv['extra_attributes']['SimInspiral'] = {
+                "source_channel": event.source_channel,
+                "destination_channel": event.destination_channel,
+                "mass1": event.mass1,
+                "mass2": event.mass2,
+                "eta": event.eta,
+                "mchirp": event.mchirp,
+                "amp_order": event.amp_order,
+                "coa_phase": event.coa_phase,
+                "spin1y": event.spin1y,
+                "spin1x": event.spin1x,
+                "spin1z": event.spin1z,
+                "spin2x": event.spin2x,
+                "spin2y": event.spin2y,
+                "spin2z": event.spin2z,
+                "geocent_end_time": event.geocent_end_time,
+                "geocent_end_time_ns": event.geocent_end_time_ns,
+                "end_time_gmst": event.end_time_gmst,
+                "f_lower": event.f_lower,
+                "f_final": event.f_final,
+                "distance": event.distance,
+                "latitude": event.latitude,
+                "longitude": event.longitude,
+                "polarization": event.polarization,
+                "inclination": event.inclination,
+                "theta0": event.theta0,
+                "phi0": event.phi0,
+                "waveform": event.waveform,
+                "numrel_mode_min": event.numrel_mode_min,
+                "numrel_mode_max": event.numrel_mode_max,
+                "numrel_data": event.numrel_data,
+                "source": event.source,
+                "taper": event.taper,
+                "bandpass": event.bandpass,
+                "alpha": event.alpha,
+                "beta": event.beta,
+                "psi0": event.psi0,
+                "psi3": event.psi3,
+                "alpha1": event.alpha1,
+                "alpha2": event.alpha2,
+                "alpha3": event.alpha3,
+                "alpha4": event.alpha4,
+                "alpha5": event.alpha5,
+                "alpha6": event.alpha6,
+                "g_end_time": event.g_end_time,
+                "g_end_time_ns": event.g_end_time_ns,
+                "h_end_time": event.h_end_time,
+                "h_end_time_ns": event.h_end_time_ns,
+                "l_end_time": event.l_end_time,
+                "l_end_time_ns": event.l_end_time_ns,
+                "t_end_time": event.t_end_time,
+                "t_end_time_ns": event.t_end_time_ns,
+                "v_end_time": event.v_end_time,
+                "v_end_time_ns": event.v_end_time_ns,
+                "eff_dist_g": event.eff_dist_g,
+                "eff_dist_h": event.eff_dist_h,
+                "eff_dist_l": event.eff_dist_l,
+                "eff_dist_t": event.eff_dist_t,
+                "eff_dist_v": event.eff_dist_v,
+            }
+    except:
+        pass
     try:
         # MultiBurstEvent
         rv['extra_attributes']['MultiBurst'] = {
diff --git a/gracedb/views.py b/gracedb/views.py
index 3f1c0b4d4..b26e396e4 100644
--- a/gracedb/views.py
+++ b/gracedb/views.py
@@ -310,6 +310,8 @@ def view(request, event):
         templates.insert(0, 'gracedb/event_detail_GRB.html')
     elif event.pipeline.name.startswith('CWB'):
         templates.insert(0, 'gracedb/event_detail_CWB.html')
+    elif event.pipeline.name in ['HardwareInjection',]:
+        templates.insert(0, 'gracedb/event_detail_injection.html')
 
     return render_to_response(templates, context, context_instance=RequestContext(request))
 
diff --git a/migrations/auth/0008_add_siminspiral_event_view_permission.py b/migrations/auth/0008_add_siminspiral_event_view_permission.py
new file mode 100644
index 000000000..337a5d27e
--- /dev/null
+++ b/migrations/auth/0008_add_siminspiral_event_view_permission.py
@@ -0,0 +1,56 @@
+# -*- coding: utf-8 -*-
+from south.utils import datetime_utils as datetime
+from south.db import db
+from south.v2 import DataMigration
+from django.db import models
+
+class Migration(DataMigration):
+
+    def forwards(self, orm):
+        content_type = orm['contenttypes.contenttype'].objects.get(app_label='gracedb', model='SimInspiralEvent')
+        orm.Permission.objects.create(codename='view_siminspiralevent', name='Can view siminspiral', content_type=content_type)
+
+    def backwards(self, orm):
+        orm.Permission.objects.get(codename='view_siminspiralevent').delete()
+
+    models = {
+        u'auth.group': {
+            'Meta': {'object_name': 'Group'},
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
+            'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
+        },
+        u'auth.permission': {
+            'Meta': {'ordering': "(u'content_type__app_label', u'content_type__model', u'codename')", 'unique_together': "((u'content_type', u'codename'),)", 'object_name': 'Permission'},
+            'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['contenttypes.ContentType']"}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
+        },
+        u'auth.user': {
+            'Meta': {'object_name': 'User'},
+            'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+            'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
+            'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
+            'groups': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "u'user_set'", 'blank': 'True', 'to': u"orm['auth.Group']"}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+            'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+            'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
+            'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+            'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "u'user_set'", 'blank': 'True', 'to': u"orm['auth.Permission']"}),
+            'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
+        },
+        u'contenttypes.contenttype': {
+            'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
+            'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
+        }
+    }
+
+    complete_apps = ['auth']
+    symmetrical = True
diff --git a/templates/gracedb/event_detail_injection.html b/templates/gracedb/event_detail_injection.html
new file mode 100644
index 000000000..fd3d6f2f0
--- /dev/null
+++ b/templates/gracedb/event_detail_injection.html
@@ -0,0 +1,86 @@
+{% extends "gracedb/event_detail.html" %}
+{% load timeutil %}
+{% load scientific %}
+
+{# Analysis-specific attributes for an LM event#}
+{% block analysis_specific %}
+
+<div id="container" style="display:table;width:100%">
+<div style="display:table-row;width:100%">
+<h2>SimInspiral Table</h2>
+
+<!-- there are 59 total attributes. Let's split into two tables. -->
+
+<div style="display:table-cell;float:left;width:50%;">
+<table> 
+    <tr> <th> Source Channel </th> <td> {{object.source_channel}} </td> </tr>
+    <tr> <th> Destination Channel </th> <td> {{object.destination_channel}} </td> </tr> 
+    <tr> <th> mass1 </th> <td> {{object.mass1|floatformat:"-4"}} </td> </tr>
+    <tr> <th> mass2 </th> <td> {{object.mass2|floatformat:"-4"}} </td> </tr> 
+    <tr> <th> eta </th> <td> {{object.eta|floatformat:"-4"}} </td> </tr> 
+    <tr> <th> mchirp </th> <td> {{object.mchirp|floatformat:"-4"}} </td> </tr> 
+    <tr> <th> amp_order </th> <td> {{object.amp_order|floatformat:"-4"}} </td> </tr> 
+    <tr> <th> coa_phase </th> <td> {{object.coa_phase|floatformat:"-4"}} </td> </tr> 
+    <tr> <th> spin1x </th> <td> {{object.spin1x|floatformat:"-4"}} </td> </tr> 
+    <tr> <th> spin1y </th> <td> {{object.spin1y|floatformat:"-4"}} </td> </tr> 
+    <tr> <th> spin1z </th> <td> {{object.spin1z|floatformat:"-4"}} </td> </tr> 
+    <tr> <th> spin2x </th> <td> {{object.spin2x|floatformat:"-4"}} </td> </tr> 
+    <tr> <th> spin2y </th> <td> {{object.spin2y|floatformat:"-4"}} </td> </tr> 
+    <tr> <th> spin2z </th> <td> {{object.spin2z|floatformat:"-4"}} </td> </tr> 
+    <tr> <th> geocent_end_time </th> <td> {{object.geocent_end_time}} </td> </tr> 
+    <tr> <th> geocent_end_time_ns </th> <td> {{object.geocent_end_time_ns}} </td> </tr> 
+    <tr> <th> end_time_gmst </th> <td> {{object.end_time_gmst}} </td> </tr> 
+    <tr> <th> f_lower </th> <td> {{object.f_lower|floatformat:"-4"}} </td> </tr> 
+    <tr> <th> f_final </th> <td> {{object.f_final|floatformat:"-4"}} </td> </tr> 
+    <tr> <th> distance </th> <td> {{object.distance|floatformat:"-4"}} </td> </tr> 
+    <tr> <th> latitude </th> <td> {{object.latitude|floatformat:"-4"}} </td> </tr> 
+    <tr> <th> longitude </th> <td> {{object.longitude|floatformat:"-4"}} </td> </tr> 
+    <tr> <th> polarization </th> <td> {{object.polarization|floatformat:"-4"}} </td> </tr> 
+    <tr> <th> inclination </th> <td> {{object.inclination|floatformat:"-4"}} </td> </tr> 
+    <tr> <th> theta0 </th> <td> {{object.theta0|floatformat:"-4"}} </td> </tr> 
+    <tr> <th> phi0 </th> <td> {{object.phi0|floatformat:"-4"}} </td> </tr> 
+    <tr> <th> waveform </th> <td> {{object.waveform}} </td> </tr> 
+    <tr> <th> numrel_mode_min </th> <td> {{object.numrel_mode_min}} </td> </tr> 
+    <tr> <th> numrel_mode_max </th> <td> {{object.numrel_mode_max}} </td> </tr> 
+</table>
+</div>
+
+<div style="display:table-cell;float:right;width:50%">
+
+<table>
+    <tr> <th> numrel_data </th> <td> {{object.numrel_data}} </td> </tr> 
+    <tr> <th> source </th> <td> {{object.source}} </td> </tr> 
+    <tr> <th> taper </th> <td> {{object.taper}} </td> </tr> 
+    <tr> <th> bandpass </th> <td> {{object.bandpass}} </td> </tr> 
+    <tr> <th> alpha </th> <td> {{object.alpha|floatformat:"-4"}} </td> </tr> 
+    <tr> <th> beta </th> <td> {{object.beta|floatformat:"-4"}} </td> </tr> 
+    <tr> <th> psi0 </th> <td> {{object.psi0|floatformat:"-4"}} </td> </tr> 
+    <tr> <th> psi3 </th> <td> {{object.psi3|floatformat:"-4"}} </td> </tr> 
+    <tr> <th> alpha1 </th> <td> {{object.alpha1|floatformat:"-4"}} </td> </tr> 
+    <tr> <th> alpha2 </th> <td> {{object.alpha2|floatformat:"-4"}} </td> </tr> 
+    <tr> <th> alpha3 </th> <td> {{object.alpha3|floatformat:"-4"}} </td> </tr> 
+    <tr> <th> alpha4 </th> <td> {{object.alpha4|floatformat:"-4"}} </td> </tr> 
+    <tr> <th> alpha5 </th> <td> {{object.alpha5|floatformat:"-4"}} </td> </tr> 
+    <tr> <th> alpha6 </th> <td> {{object.alpha6|floatformat:"-4"}} </td> </tr> 
+    <tr> <th> g_end_time </th> <td> {{object.g_end_time}} </td> </tr> 
+    <tr> <th> g_end_time_ns </th> <td> {{object.g_end_time_ns}} </td> </tr> 
+    <tr> <th> h_end_time </th> <td> {{object.h_end_time}} </td> </tr> 
+    <tr> <th> h_end_time_ns </th> <td> {{object.h_end_time_ns}} </td> </tr> 
+    <tr> <th> l_end_time </th> <td> {{object.l_end_time}} </td> </tr> 
+    <tr> <th> l_end_time_ns </th> <td> {{object.l_end_time_ns}} </td> </tr> 
+    <tr> <th> t_end_time </th> <td> {{object.t_end_time}} </td> </tr> 
+    <tr> <th> t_end_time_ns </th> <td> {{object.t_end_time_ns}} </td> </tr> 
+    <tr> <th> v_end_time </th> <td> {{object.v_end_time}} </td> </tr> 
+    <tr> <th> v_end_time_ns </th> <td> {{object.v_end_time_ns}} </td> </tr> 
+    <tr> <th> eff_dist_g </th> <td> {{object.eff_dist_g|floatformat:"-4"}} </td> </tr> 
+    <tr> <th> eff_dist_h </th> <td> {{object.eff_dist_h|floatformat:"-4"}} </td> </tr> 
+    <tr> <th> eff_dist_l </th> <td> {{object.eff_dist_l|floatformat:"-4"}} </td> </tr> 
+    <tr> <th> eff_dist_t </th> <td> {{object.eff_dist_t|floatformat:"-4"}} </td> </tr> 
+    <tr> <th> eff_dist_v </th> <td> {{object.eff_dist_v|floatformat:"-4"}} </td> </tr> 
+</table>
+</div> 
+
+</div>
+</div> <!-- container -->
+
+{% endblock %}
-- 
GitLab