From 92ea79a66d1611680fb975060de554b0af938cb7 Mon Sep 17 00:00:00 2001 From: Jesse London Date: Mon, 16 Mar 2015 19:02:51 -0500 Subject: [PATCH 1/2] potential generalized modeling of campaigns, content, etc. --- ...n__add_unique_contentvehiclecampaign_ur.py | 260 ++++++++++++++++++ magnus/models/__init__.py | 96 ++++++- 2 files changed, 355 insertions(+), 1 deletion(-) create mode 100644 magnus/migrations/0003_auto__add_contentvehiclecampaign__add_unique_contentvehiclecampaign_ur.py diff --git a/magnus/migrations/0003_auto__add_contentvehiclecampaign__add_unique_contentvehiclecampaign_ur.py b/magnus/migrations/0003_auto__add_contentvehiclecampaign__add_unique_contentvehiclecampaign_ur.py new file mode 100644 index 0000000..923ecf1 --- /dev/null +++ b/magnus/migrations/0003_auto__add_contentvehiclecampaign__add_unique_contentvehiclecampaign_ur.py @@ -0,0 +1,260 @@ +# -*- coding: utf-8 -*- +from south.db import db +from south.v2 import SchemaMigration + + +class Migration(SchemaMigration): + + def forwards(self, orm): + # Adding model 'ContentVehicleCampaign' + db.create_table('content_vehicle_campaigns', ( + ('content_vehicle_campaign_id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), + ('urn', self.gf('django.db.models.fields.CharField')(max_length=30)), + ('content_vehicle', self.gf('django.db.models.fields.related.ForeignKey')(related_name='content_vehicle_campaigns', to=orm['magnus.ContentVehicle'])), + ('campaign', self.gf('django.db.models.fields.related.ForeignKey')(related_name='content_vehicle_campaigns', to=orm['magnus.Campaign'])), + ('created', self.gf('django.db.models.fields.DateTimeField')(auto_now_add=True, blank=True)), + ('updated', self.gf('django.db.models.fields.DateTimeField')(auto_now=True, blank=True)), + )) + db.send_create_signal(u'magnus', ['ContentVehicleCampaign']) + + # Adding unique constraint on 'ContentVehicleCampaign', fields ['urn', 'content_vehicle'] + db.create_unique('content_vehicle_campaigns', ['urn', 'content_vehicle_id']) + + # Adding model 'ContentVehicle' + db.create_table('content_vehicles', ( + ('codename', self.gf('django.db.models.fields.SlugField')(max_length=50, primary_key=True)), + ('name', self.gf('django.db.models.fields.CharField')(max_length=100)), + ('vehicle_owner', self.gf('django.db.models.fields.related.ForeignKey')(related_name='content_vehicles', to=orm['magnus.VehicleOwner'])), + ('created', self.gf('django.db.models.fields.DateTimeField')(auto_now_add=True, blank=True)), + ('updated', self.gf('django.db.models.fields.DateTimeField')(auto_now=True, blank=True)), + )) + db.send_create_signal(u'magnus', ['ContentVehicle']) + + # Adding model 'VehicleOwner' + db.create_table('vehicle_owners', ( + ('codename', self.gf('django.db.models.fields.SlugField')(max_length=25, primary_key=True)), + ('created', self.gf('django.db.models.fields.DateTimeField')(auto_now_add=True, blank=True)), + ('updated', self.gf('django.db.models.fields.DateTimeField')(auto_now=True, blank=True)), + )) + db.send_create_signal(u'magnus', ['VehicleOwner']) + + # Adding model 'ClientContent' + db.create_table('client_content', ( + ('client_content_id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), + ('name', self.gf('django.db.models.fields.CharField')(max_length=256, null=True, blank=True)), + ('description', self.gf('django.db.models.fields.CharField')(max_length=1024, blank=True)), + ('url', self.gf('django.db.models.fields.URLField')(max_length=2048)), + ('client', self.gf('django.db.models.fields.related.ForeignKey')(related_name='client_content', to=orm['magnus.Client'])), + ('created', self.gf('django.db.models.fields.DateTimeField')(auto_now_add=True, blank=True)), + ('updated', self.gf('django.db.models.fields.DateTimeField')(auto_now=True, blank=True)), + )) + db.send_create_signal(u'magnus', ['ClientContent']) + + # Adding unique constraint on 'ClientContent', fields ['name', 'client'] + db.create_unique('client_content', ['name', 'client_id']) + + # Adding unique constraint on 'ClientContent', fields ['url', 'client'] + db.create_unique('client_content', ['url', 'client_id']) + + # Deleting field 'Campaign.client' + db.delete_column('campaigns', 'client_id') + + # Adding field 'Campaign.client_content' + db.add_column('campaigns', 'client_content', + self.gf('django.db.models.fields.related.ForeignKey')(related_name='campaigns', to=orm['magnus.ClientContent'])) + + def backwards(self, orm): + # Removing unique constraint on 'ClientContent', fields ['url', 'client'] + db.delete_unique('client_content', ['url', 'client_id']) + + # Removing unique constraint on 'ClientContent', fields ['name', 'client'] + db.delete_unique('client_content', ['name', 'client_id']) + + # Removing unique constraint on 'ContentVehicleCampaign', fields ['urn', 'content_vehicle'] + db.delete_unique('content_vehicle_campaigns', ['urn', 'content_vehicle_id']) + + # Deleting model 'ContentVehicleCampaign' + db.delete_table('content_vehicle_campaigns') + + # Deleting model 'ContentVehicle' + db.delete_table('content_vehicles') + + # Deleting model 'VehicleOwner' + db.delete_table('vehicle_owners') + + # Deleting model 'ClientContent' + db.delete_table('client_content') + + # Adding field 'Campaign.client' + db.add_column('campaigns', 'client', + self.gf('django.db.models.fields.related.ForeignKey')(related_name='campaigns', to=orm['magnus.Client'])) + + # Deleting field 'Campaign.client_content' + db.delete_column('campaigns', 'client_content_id') + + models = { + u'magnus.campaign': { + 'Meta': {'object_name': 'Campaign', 'db_table': "'campaigns'"}, + 'campaign_id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'client_content': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'campaigns'", 'to': u"orm['magnus.ClientContent']"}), + 'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'updated': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}) + }, + u'magnus.client': { + 'Meta': {'object_name': 'Client', 'db_table': "'clients'"}, + 'client_id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'codename': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '50', 'blank': 'True'}), + 'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), + 'fb_app_users': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'connected_clients'", 'blank': 'True', 'through': u"orm['magnus.ClientFBAppUser']", 'to': u"orm['magnus.FBAppUser']"}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'updated': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}) + }, + u'magnus.clientcontent': { + 'Meta': {'unique_together': "(('name', 'client'), ('url', 'client'))", 'object_name': 'ClientContent', 'db_table': "'client_content'"}, + 'client': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'client_content'", 'to': u"orm['magnus.Client']"}), + 'client_content_id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), + 'description': ('django.db.models.fields.CharField', [], {'max_length': '1024', 'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '256', 'null': 'True', 'blank': 'True'}), + 'updated': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}), + 'url': ('django.db.models.fields.URLField', [], {'max_length': '2048'}) + }, + u'magnus.clientfbappuser': { + 'Meta': {'unique_together': "(('client', 'fb_app_user'),)", 'object_name': 'ClientFBAppUser', 'db_table': "'clients_fb_app_users'"}, + 'client': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'+'", 'to': u"orm['magnus.Client']"}), + 'client_app_user_id': ('magnus.models.fields.BigSerialField', [], {'primary_key': 'True'}), + 'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), + 'fb_app_user': ('magnus.models.fields.FlexibleForeignKey', [], {'related_name': "'+'", 'to': u"orm['magnus.FBAppUser']"}), + 'updated': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}) + }, + u'magnus.contentvehicle': { + 'Meta': {'ordering': "('codename',)", 'object_name': 'ContentVehicle', 'db_table': "'content_vehicles'"}, + 'codename': ('django.db.models.fields.SlugField', [], {'max_length': '50', 'primary_key': 'True'}), + 'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'updated': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}), + 'vehicle_owner': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'content_vehicles'", 'to': u"orm['magnus.VehicleOwner']"}) + }, + u'magnus.contentvehiclecampaign': { + 'Meta': {'unique_together': "(('urn', 'content_vehicle'),)", 'object_name': 'ContentVehicleCampaign', 'db_table': "'content_vehicle_campaigns'"}, + 'campaign': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'content_vehicle_campaigns'", 'to': u"orm['magnus.Campaign']"}), + 'content_vehicle': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'content_vehicle_campaigns'", 'to': u"orm['magnus.ContentVehicle']"}), + 'content_vehicle_campaign_id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), + 'updated': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}), + 'urn': ('django.db.models.fields.CharField', [], {'max_length': '30'}) + }, + u'magnus.efapikey': { + 'Meta': {'object_name': 'EFApiKey', 'db_table': "'ef_api_keys'"}, + 'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), + 'ef_api_user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'efapikeys'", 'db_column': "'ef_api_user_name'", 'to': u"orm['magnus.EFApiUser']"}), + 'ef_app': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'efapikeys'", 'db_column': "'ef_app_name'", 'to': u"orm['magnus.EFApp']"}), + 'key': ('django.db.models.fields.SlugField', [], {'default': "'3f0988181c86edc40d25ec077ee47dcb57b9fd78'", 'max_length': '40', 'primary_key': 'True', 'db_column': "'ef_api_key'"}), + 'updated': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}) + }, + u'magnus.efapiuser': { + 'Meta': {'object_name': 'EFApiUser', 'db_table': "'ef_api_users'"}, + 'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), + 'name': ('django.db.models.fields.SlugField', [], {'max_length': '30', 'primary_key': 'True', 'db_column': "'ef_api_user_name'"}), + 'updated': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}) + }, + u'magnus.efapp': { + 'Meta': {'object_name': 'EFApp', 'db_table': "'ef_apps'"}, + 'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), + 'name': ('django.db.models.fields.SlugField', [], {'max_length': '30', 'primary_key': 'True', 'db_column': "'ef_app_name'"}), + 'updated': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}) + }, + u'magnus.efuser': { + 'Meta': {'object_name': 'EFUser', 'db_table': "'ef_users'"}, + 'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), + 'efid': ('magnus.models.fields.BigSerialField', [], {'primary_key': 'True'}), + 'email': ('django.db.models.fields.EmailField', [], {'max_length': '254', 'unique': 'True', 'null': 'True', 'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}), + 'updated': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}) + }, + u'magnus.efuservisitoragent': { + 'Meta': {'unique_together': "(('ef_user', 'visitor_agent'),)", 'object_name': 'EFUserVisitorAgent', 'db_table': "'ef_users_visitor_agents'"}, + 'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), + 'ef_user': ('magnus.models.fields.FlexibleForeignKey', [], {'related_name': "'+'", 'db_column': "'efid'", 'to': u"orm['magnus.EFUser']"}), + 'ef_user_visitor_agent_id': ('magnus.models.fields.BigSerialField', [], {'primary_key': 'True'}), + 'updated': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}), + 'visitor_agent': ('magnus.models.fields.FlexibleForeignKey', [], {'related_name': "'+'", 'to': u"orm['magnus.VisitorAgent']"}) + }, + u'magnus.event': { + 'Meta': {'ordering': "('event_datetime',)", 'object_name': 'Event', 'db_table': "'events'"}, + 'campaign': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'events'", 'null': 'True', 'to': u"orm['magnus.Campaign']"}), + 'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), + 'data': ('magnus.models.fields.JSONField', [], {}), + 'event_datetime': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now', 'db_index': 'True'}), + 'event_id': ('magnus.models.fields.BigSerialField', [], {'primary_key': 'True'}), + 'event_type': ('django.db.models.fields.CharField', [], {'max_length': '64'}), + 'updated': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}), + 'visit': ('magnus.models.fields.FlexibleForeignKey', [], {'related_name': "'events'", 'to': u"orm['magnus.Visit']"}) + }, + u'magnus.fbapp': { + 'Meta': {'ordering': "('namespace',)", 'object_name': 'FBApp', 'db_table': "'fb_apps'"}, + 'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), + 'current_api': ('django.db.models.fields.DecimalField', [], {'default': "'2.2'", 'max_digits': '3', 'decimal_places': '1'}), + 'current_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'current_apps'", 'blank': 'True', 'db_table': "'fb_apps_fb_permissions'", 'to': u"orm['magnus.FBPermission']"}), + 'fb_app_id': ('django.db.models.fields.BigIntegerField', [], {'primary_key': 'True'}), + 'namespace': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '255'}), + 'secret': ('django.db.models.fields.CharField', [], {'max_length': '32'}), + 'updated': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}) + }, + u'magnus.fbappuser': { + 'Meta': {'unique_together': "(('fbid', 'fb_app'), ('ef_user', 'fb_app'))", 'object_name': 'FBAppUser', 'db_table': "'fb_app_users'"}, + 'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), + 'ef_user': ('magnus.models.fields.FlexibleForeignKey', [], {'related_name': "'fb_app_users'", 'db_column': "'efid'", 'to': u"orm['magnus.EFUser']"}), + 'fb_app': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'fb_app_users'", 'to': u"orm['magnus.FBApp']"}), + 'fb_app_user_id': ('magnus.models.fields.BigSerialField', [], {'primary_key': 'True'}), + 'fbid': ('django.db.models.fields.BigIntegerField', [], {}), + 'updated': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}) + }, + u'magnus.fbpermission': { + 'Meta': {'ordering': "('code',)", 'object_name': 'FBPermission', 'db_table': "'fb_permissions'"}, + 'code': ('django.db.models.fields.SlugField', [], {'max_length': '64', 'primary_key': 'True'}), + 'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), + 'updated': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}) + }, + u'magnus.fbusertoken': { + 'Meta': {'unique_together': "(('api', 'fb_app_user'),)", 'object_name': 'FBUserToken', 'db_table': "'fb_user_tokens'"}, + 'access_token': ('django.db.models.fields.TextField', [], {}), + 'api': ('django.db.models.fields.DecimalField', [], {'default': "'2.2'", 'max_digits': '3', 'decimal_places': '1'}), + 'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), + 'expiration': ('django.db.models.fields.DateTimeField', [], {'db_index': 'True'}), + 'fb_app_user': ('magnus.models.fields.FlexibleForeignKey', [], {'related_name': "'fb_user_tokens'", 'to': u"orm['magnus.FBAppUser']"}), + 'fb_user_token_id': ('magnus.models.fields.BigSerialField', [], {'primary_key': 'True'}), + 'updated': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}) + }, + u'magnus.vehicleowner': { + 'Meta': {'ordering': "('codename',)", 'object_name': 'VehicleOwner', 'db_table': "'vehicle_owners'"}, + 'codename': ('django.db.models.fields.SlugField', [], {'max_length': '25', 'primary_key': 'True'}), + 'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), + 'updated': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}) + }, + u'magnus.visit': { + 'Meta': {'unique_together': "(('session_id', 'fb_app'),)", 'object_name': 'Visit', 'db_table': "'visits'"}, + 'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), + 'fb_app': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'visits'", 'null': 'True', 'to': u"orm['magnus.FBApp']"}), + 'fbid': ('django.db.models.fields.BigIntegerField', [], {'null': 'True'}), + 'ip': ('django.db.models.fields.GenericIPAddressField', [], {'max_length': '39'}), + 'referer': ('django.db.models.fields.CharField', [], {'max_length': '1028', 'blank': 'True'}), + 'session_id': ('django.db.models.fields.CharField', [], {'max_length': '40'}), + 'source': ('django.db.models.fields.SlugField', [], {'max_length': '50', 'blank': 'True'}), + 'updated': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}), + 'visit_id': ('magnus.models.fields.BigSerialField', [], {'primary_key': 'True'}), + 'visitor_agent': ('magnus.models.fields.FlexibleForeignKey', [], {'related_name': "'visits'", 'to': u"orm['magnus.VisitorAgent']"}) + }, + u'magnus.visitoragent': { + 'Meta': {'object_name': 'VisitorAgent', 'db_table': "'visitor_agents'"}, + 'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), + 'ef_users': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'visitor_agents'", 'blank': 'True', 'through': u"orm['magnus.EFUserVisitorAgent']", 'to': u"orm['magnus.EFUser']"}), + 'updated': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}), + 'user_agent': ('django.db.models.fields.CharField', [], {'max_length': '1028', 'blank': 'True'}), + 'uuid': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '40', 'blank': 'True'}), + 'visitor_agent_id': ('magnus.models.fields.BigSerialField', [], {'primary_key': 'True'}) + } + } + + complete_apps = ['magnus'] diff --git a/magnus/models/__init__.py b/magnus/models/__init__.py index 8081c0e..55308c2 100644 --- a/magnus/models/__init__.py +++ b/magnus/models/__init__.py @@ -147,11 +147,105 @@ def __repr__(self): return '<{}: {} [{}]>'.format(self.__class__.__name__, self.fb_app_user_id, self.api) +class ClientContent(base.BaseModel): + + client_content_id = models.AutoField(primary_key=True) + client = models.ForeignKey('magnus.Client', related_name='client_content') + name = models.CharField(max_length=256, blank=True, null=True) + description = models.CharField(max_length=1024, blank=True) + url = models.URLField(max_length=2048) + + class Meta(base.BaseModel.Meta): + db_table = 'client_content' + unique_together = (('name', 'client'), ('url', 'client')) + + def __unicode__(self): + return self.name or self.url + + def __repr__(self): + signature = self.url[:47] + '...' if len(self.url) > 50 else self.url + if self.name: + signature += ' [{}]'.format(self.name) + return '<{}: {}>'.format(self.__class__.__name__, signature) + + +class VehicleOwner(base.BaseModel): + + codename = models.SlugField(max_length=25, primary_key=True) + + objects = manager.TypeObjectManager() + + class Codenames(objects.Types): + + EDGEFLIP = 'edgeflip' + FACEBOOK = 'facebook' + + def __str__(self): + return self.value + + class Meta(base.BaseModel.Meta): + db_table = 'vehicle_owners' + ordering = ('codename',) + + def __unicode__(self): + return self.codename + + +class ContentVehicle(base.BaseModel): + + name = models.CharField(max_length=100) + codename = models.SlugField(primary_key=True) + vehicle_owner = models.ForeignKey('magnus.VehicleOwner', related_name='content_vehicles') + + objects = manager.TypeObjectManager() + + class Codenames(objects.Types): + + EF_TARGETED_SHARE = 'ef_targeted_share' + FB_NOTIFICATION = 'fb_notification' + FB_POST = 'fb_post' + + def __str__(self): + return self.value + + class Meta(base.BaseModel.Meta): + db_table = 'content_vehicles' + ordering = ('codename',) + + def __unicode__(self): + return self.name + + def __repr__(self): + return u"<{}: {}>".format(self.__class__.__name__, self.codename) + + +class ContentVehicleCampaign(base.BaseModel): + + related_name = 'content_vehicle_campaigns' + + content_vehicle_campaign_id = models.AutoField(primary_key=True) + urn = models.CharField("Vehicle Resource Name", max_length=30) # indexed below + content_vehicle = models.ForeignKey('magnus.ContentVehicle', related_name=related_name) + campaign = models.ForeignKey('magnus.Campaign', related_name=related_name) + + class Meta(base.BaseModel.Meta): + db_table = 'content_vehicle_campaigns' + unique_together = ('urn', 'content_vehicle') + + def __unicode__(self): + return self.urn + + def __repr__(self): + return u"<{}: {} [{}]>".format(self.__class__.__name__, + self.urn, + self.content_vehicle.codename) + + class Campaign(base.BaseModel): campaign_id = models.AutoField(primary_key=True) name = models.CharField('Campaign Name', max_length=255) - client = models.ForeignKey('magnus.Client', related_name='campaigns') + client_content = models.ForeignKey('magnus.ClientContent', related_name='campaigns') class Meta(base.BaseModel.Meta): db_table = 'campaigns' From 74e47762c02470940a13ad5407e32b4ca12dc61c Mon Sep 17 00:00:00 2001 From: Jesse London Date: Thu, 19 Mar 2015 19:34:31 -0500 Subject: [PATCH 2/2] removed `campaigns` table once and for all(?!), removed `vehicle_owners`, and beefed up `content_vehicles` --- ...ehicle__add_unique_contentvehicle_urn_.py} | 118 ++++++++-------- magnus/models/__init__.py | 127 ++++++++++-------- 2 files changed, 128 insertions(+), 117 deletions(-) rename magnus/migrations/{0003_auto__add_contentvehiclecampaign__add_unique_contentvehiclecampaign_ur.py => 0003_auto__del_campaign__add_contentvehicle__add_unique_contentvehicle_urn_.py} (79%) diff --git a/magnus/migrations/0003_auto__add_contentvehiclecampaign__add_unique_contentvehiclecampaign_ur.py b/magnus/migrations/0003_auto__del_campaign__add_contentvehicle__add_unique_contentvehicle_urn_.py similarity index 79% rename from magnus/migrations/0003_auto__add_contentvehiclecampaign__add_unique_contentvehiclecampaign_ur.py rename to magnus/migrations/0003_auto__del_campaign__add_contentvehicle__add_unique_contentvehicle_urn_.py index 923ecf1..7bf367e 100644 --- a/magnus/migrations/0003_auto__add_contentvehiclecampaign__add_unique_contentvehiclecampaign_ur.py +++ b/magnus/migrations/0003_auto__del_campaign__add_contentvehicle__add_unique_contentvehicle_urn_.py @@ -6,45 +6,40 @@ class Migration(SchemaMigration): def forwards(self, orm): - # Adding model 'ContentVehicleCampaign' - db.create_table('content_vehicle_campaigns', ( - ('content_vehicle_campaign_id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), - ('urn', self.gf('django.db.models.fields.CharField')(max_length=30)), - ('content_vehicle', self.gf('django.db.models.fields.related.ForeignKey')(related_name='content_vehicle_campaigns', to=orm['magnus.ContentVehicle'])), - ('campaign', self.gf('django.db.models.fields.related.ForeignKey')(related_name='content_vehicle_campaigns', to=orm['magnus.Campaign'])), - ('created', self.gf('django.db.models.fields.DateTimeField')(auto_now_add=True, blank=True)), - ('updated', self.gf('django.db.models.fields.DateTimeField')(auto_now=True, blank=True)), - )) - db.send_create_signal(u'magnus', ['ContentVehicleCampaign']) - - # Adding unique constraint on 'ContentVehicleCampaign', fields ['urn', 'content_vehicle'] - db.create_unique('content_vehicle_campaigns', ['urn', 'content_vehicle_id']) + # Deleting model 'Campaign' + db.delete_table('campaigns') # Adding model 'ContentVehicle' db.create_table('content_vehicles', ( - ('codename', self.gf('django.db.models.fields.SlugField')(max_length=50, primary_key=True)), - ('name', self.gf('django.db.models.fields.CharField')(max_length=100)), - ('vehicle_owner', self.gf('django.db.models.fields.related.ForeignKey')(related_name='content_vehicles', to=orm['magnus.VehicleOwner'])), + ('content_vehicle_id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), + ('urn', self.gf('django.db.models.fields.CharField')(max_length=30)), + ('content_vehicle_type', self.gf('django.db.models.fields.related.ForeignKey')(related_name='content_vehicles', to=orm['magnus.ContentVehicleType'])), + ('intermediate_vehicle', self.gf('django.db.models.fields.related.ForeignKey')(blank=True, related_name='linking_vehicles', null=True, to=orm['magnus.ContentVehicle'])), + ('client_content', self.gf('django.db.models.fields.related.ForeignKey')(related_name='content_vehicles', to=orm['magnus.ClientContent'])), ('created', self.gf('django.db.models.fields.DateTimeField')(auto_now_add=True, blank=True)), ('updated', self.gf('django.db.models.fields.DateTimeField')(auto_now=True, blank=True)), )) db.send_create_signal(u'magnus', ['ContentVehicle']) - # Adding model 'VehicleOwner' - db.create_table('vehicle_owners', ( - ('codename', self.gf('django.db.models.fields.SlugField')(max_length=25, primary_key=True)), + # Adding unique constraint on 'ContentVehicle', fields ['urn', 'content_vehicle_type'] + db.create_unique('content_vehicles', ['urn', 'content_vehicle_type_id']) + + # Adding model 'ContentVehicleType' + db.create_table('content_vehicle_types', ( + ('codename', self.gf('django.db.models.fields.SlugField')(max_length=50, primary_key=True)), + ('name', self.gf('django.db.models.fields.CharField')(max_length=100)), ('created', self.gf('django.db.models.fields.DateTimeField')(auto_now_add=True, blank=True)), ('updated', self.gf('django.db.models.fields.DateTimeField')(auto_now=True, blank=True)), )) - db.send_create_signal(u'magnus', ['VehicleOwner']) + db.send_create_signal(u'magnus', ['ContentVehicleType']) # Adding model 'ClientContent' db.create_table('client_content', ( ('client_content_id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), + ('client', self.gf('django.db.models.fields.related.ForeignKey')(related_name='client_content', to=orm['magnus.Client'])), ('name', self.gf('django.db.models.fields.CharField')(max_length=256, null=True, blank=True)), ('description', self.gf('django.db.models.fields.CharField')(max_length=1024, blank=True)), ('url', self.gf('django.db.models.fields.URLField')(max_length=2048)), - ('client', self.gf('django.db.models.fields.related.ForeignKey')(related_name='client_content', to=orm['magnus.Client'])), ('created', self.gf('django.db.models.fields.DateTimeField')(auto_now_add=True, blank=True)), ('updated', self.gf('django.db.models.fields.DateTimeField')(auto_now=True, blank=True)), )) @@ -56,12 +51,13 @@ def forwards(self, orm): # Adding unique constraint on 'ClientContent', fields ['url', 'client'] db.create_unique('client_content', ['url', 'client_id']) - # Deleting field 'Campaign.client' - db.delete_column('campaigns', 'client_id') + # Deleting field 'Event.campaign' + db.delete_column('events', 'campaign_id') - # Adding field 'Campaign.client_content' - db.add_column('campaigns', 'client_content', - self.gf('django.db.models.fields.related.ForeignKey')(related_name='campaigns', to=orm['magnus.ClientContent'])) + # Adding field 'Event.content_vehicle' + db.add_column('events', 'content_vehicle', + self.gf('django.db.models.fields.related.ForeignKey')(related_name='events', null=True, to=orm['magnus.ContentVehicle']), + keep_default=False) def backwards(self, orm): # Removing unique constraint on 'ClientContent', fields ['url', 'client'] @@ -70,37 +66,37 @@ def backwards(self, orm): # Removing unique constraint on 'ClientContent', fields ['name', 'client'] db.delete_unique('client_content', ['name', 'client_id']) - # Removing unique constraint on 'ContentVehicleCampaign', fields ['urn', 'content_vehicle'] - db.delete_unique('content_vehicle_campaigns', ['urn', 'content_vehicle_id']) + # Removing unique constraint on 'ContentVehicle', fields ['urn', 'content_vehicle_type'] + db.delete_unique('content_vehicles', ['urn', 'content_vehicle_type_id']) - # Deleting model 'ContentVehicleCampaign' - db.delete_table('content_vehicle_campaigns') + # Adding model 'Campaign' + db.create_table('campaigns', ( + ('campaign_id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), + ('name', self.gf('django.db.models.fields.CharField')(max_length=255)), + ('client', self.gf('django.db.models.fields.related.ForeignKey')(related_name='campaigns', to=orm['magnus.Client'])), + ('created', self.gf('django.db.models.fields.DateTimeField')(auto_now_add=True, blank=True)), + ('updated', self.gf('django.db.models.fields.DateTimeField')(auto_now=True, blank=True)), + )) + db.send_create_signal(u'magnus', ['Campaign']) # Deleting model 'ContentVehicle' db.delete_table('content_vehicles') - # Deleting model 'VehicleOwner' - db.delete_table('vehicle_owners') + # Deleting model 'ContentVehicleType' + db.delete_table('content_vehicle_types') # Deleting model 'ClientContent' db.delete_table('client_content') - # Adding field 'Campaign.client' - db.add_column('campaigns', 'client', - self.gf('django.db.models.fields.related.ForeignKey')(related_name='campaigns', to=orm['magnus.Client'])) + # Adding field 'Event.campaign' + db.add_column('events', 'campaign', + self.gf('django.db.models.fields.related.ForeignKey')(related_name='events', null=True, to=orm['magnus.Campaign']), + keep_default=False) - # Deleting field 'Campaign.client_content' - db.delete_column('campaigns', 'client_content_id') + # Deleting field 'Event.content_vehicle' + db.delete_column('events', 'content_vehicle_id') models = { - u'magnus.campaign': { - 'Meta': {'object_name': 'Campaign', 'db_table': "'campaigns'"}, - 'campaign_id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'client_content': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'campaigns'", 'to': u"orm['magnus.ClientContent']"}), - 'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), - 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), - 'updated': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}) - }, u'magnus.client': { 'Meta': {'object_name': 'Client', 'db_table': "'clients'"}, 'client_id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), @@ -129,28 +125,28 @@ def backwards(self, orm): 'updated': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}) }, u'magnus.contentvehicle': { - 'Meta': {'ordering': "('codename',)", 'object_name': 'ContentVehicle', 'db_table': "'content_vehicles'"}, - 'codename': ('django.db.models.fields.SlugField', [], {'max_length': '50', 'primary_key': 'True'}), + 'Meta': {'unique_together': "(('urn', 'content_vehicle_type'),)", 'object_name': 'ContentVehicle', 'db_table': "'content_vehicles'"}, + 'client_content': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'content_vehicles'", 'to': u"orm['magnus.ClientContent']"}), + 'content_vehicle_id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'content_vehicle_type': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'content_vehicles'", 'to': u"orm['magnus.ContentVehicleType']"}), 'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), - 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'intermediate_vehicle': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'linking_vehicles'", 'null': 'True', 'to': u"orm['magnus.ContentVehicle']"}), 'updated': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}), - 'vehicle_owner': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'content_vehicles'", 'to': u"orm['magnus.VehicleOwner']"}) + 'urn': ('django.db.models.fields.CharField', [], {'max_length': '30'}) }, - u'magnus.contentvehiclecampaign': { - 'Meta': {'unique_together': "(('urn', 'content_vehicle'),)", 'object_name': 'ContentVehicleCampaign', 'db_table': "'content_vehicle_campaigns'"}, - 'campaign': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'content_vehicle_campaigns'", 'to': u"orm['magnus.Campaign']"}), - 'content_vehicle': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'content_vehicle_campaigns'", 'to': u"orm['magnus.ContentVehicle']"}), - 'content_vehicle_campaign_id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + u'magnus.contentvehicletype': { + 'Meta': {'ordering': "('codename',)", 'object_name': 'ContentVehicleType', 'db_table': "'content_vehicle_types'"}, + 'codename': ('django.db.models.fields.SlugField', [], {'max_length': '50', 'primary_key': 'True'}), 'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), - 'updated': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}), - 'urn': ('django.db.models.fields.CharField', [], {'max_length': '30'}) + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'updated': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}) }, u'magnus.efapikey': { 'Meta': {'object_name': 'EFApiKey', 'db_table': "'ef_api_keys'"}, 'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), 'ef_api_user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'efapikeys'", 'db_column': "'ef_api_user_name'", 'to': u"orm['magnus.EFApiUser']"}), 'ef_app': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'efapikeys'", 'db_column': "'ef_app_name'", 'to': u"orm['magnus.EFApp']"}), - 'key': ('django.db.models.fields.SlugField', [], {'default': "'3f0988181c86edc40d25ec077ee47dcb57b9fd78'", 'max_length': '40', 'primary_key': 'True', 'db_column': "'ef_api_key'"}), + 'key': ('django.db.models.fields.SlugField', [], {'default': "'7894eb6b702cede7ff8d9872bfc71b9c6260e7d5'", 'max_length': '40', 'primary_key': 'True', 'db_column': "'ef_api_key'"}), 'updated': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}) }, u'magnus.efapiuser': { @@ -183,7 +179,7 @@ def backwards(self, orm): }, u'magnus.event': { 'Meta': {'ordering': "('event_datetime',)", 'object_name': 'Event', 'db_table': "'events'"}, - 'campaign': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'events'", 'null': 'True', 'to': u"orm['magnus.Campaign']"}), + 'content_vehicle': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'events'", 'null': 'True', 'to': u"orm['magnus.ContentVehicle']"}), 'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), 'data': ('magnus.models.fields.JSONField', [], {}), 'event_datetime': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now', 'db_index': 'True'}), @@ -227,12 +223,6 @@ def backwards(self, orm): 'fb_user_token_id': ('magnus.models.fields.BigSerialField', [], {'primary_key': 'True'}), 'updated': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}) }, - u'magnus.vehicleowner': { - 'Meta': {'ordering': "('codename',)", 'object_name': 'VehicleOwner', 'db_table': "'vehicle_owners'"}, - 'codename': ('django.db.models.fields.SlugField', [], {'max_length': '25', 'primary_key': 'True'}), - 'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), - 'updated': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}) - }, u'magnus.visit': { 'Meta': {'unique_together': "(('session_id', 'fb_app'),)", 'object_name': 'Visit', 'db_table': "'visits'"}, 'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), diff --git a/magnus/models/__init__.py b/magnus/models/__init__.py index 55308c2..3b9b108 100644 --- a/magnus/models/__init__.py +++ b/magnus/models/__init__.py @@ -5,6 +5,7 @@ from decimal import Decimal from django.conf import settings +from django.core import exceptions from django.db import models from django.utils import timezone from django.utils.crypto import get_random_string @@ -148,12 +149,13 @@ def __repr__(self): class ClientContent(base.BaseModel): + """A Web resource which the client intends to promote.""" client_content_id = models.AutoField(primary_key=True) client = models.ForeignKey('magnus.Client', related_name='client_content') - name = models.CharField(max_length=256, blank=True, null=True) + name = models.CharField(max_length=256, blank=True, null=True) # indexed below description = models.CharField(max_length=1024, blank=True) - url = models.URLField(max_length=2048) + url = models.URLField(max_length=2048) # indexed below class Meta(base.BaseModel.Meta): db_table = 'client_content' @@ -166,36 +168,16 @@ def __repr__(self): signature = self.url[:47] + '...' if len(self.url) > 50 else self.url if self.name: signature += ' [{}]'.format(self.name) - return '<{}: {}>'.format(self.__class__.__name__, signature) + return u'<{}: {}>'.format(self.__class__.__name__, signature) -class VehicleOwner(base.BaseModel): +class ContentVehicleType(base.BaseModel): + """Any distinct class of Web app, view or feature, which is used to drive + user traffic to clients' content. - codename = models.SlugField(max_length=25, primary_key=True) - - objects = manager.TypeObjectManager() - - class Codenames(objects.Types): - - EDGEFLIP = 'edgeflip' - FACEBOOK = 'facebook' - - def __str__(self): - return self.value - - class Meta(base.BaseModel.Meta): - db_table = 'vehicle_owners' - ordering = ('codename',) - - def __unicode__(self): - return self.codename - - -class ContentVehicle(base.BaseModel): - - name = models.CharField(max_length=100) + """ codename = models.SlugField(primary_key=True) - vehicle_owner = models.ForeignKey('magnus.VehicleOwner', related_name='content_vehicles') + name = models.CharField(max_length=100) objects = manager.TypeObjectManager() @@ -209,7 +191,7 @@ def __str__(self): return self.value class Meta(base.BaseModel.Meta): - db_table = 'content_vehicles' + db_table = 'content_vehicle_types' ordering = ('codename',) def __unicode__(self): @@ -219,18 +201,70 @@ def __repr__(self): return u"<{}: {}>".format(self.__class__.__name__, self.codename) -class ContentVehicleCampaign(base.BaseModel): +class ContentVehicle(base.BaseModel): + """A concrete, user-consumable instance of a ContentVehicleType, such as a + particular blog post or an email using particular content. + + In some, though not all cases, a ContentVehicle is an abstraction of what is + sometimes called a "campaign", (e.g. email). However, ContentVehicles do not + make all the assumptions of a campaign. + + ContentVehicles need only identify their type, the ClientContent to which + they are intended to drive traffic, and some unique identifier -- the `urn`, + or Uniform Resource Name. (Here, the "resource" is the "vehicle". The + existing vernacular of Uniform Resource Indicators trumps anything we could + come up with. For the resource to which the content vehicle drives traffic, + see its ClientContent's `url`, or Uniform Resource Locator.) + + The vehicle URN should be the canonical, and minimally necessary, identifier + with which the vehicle may be retrieved. For example, the numeric string + `10206332515695927` is necessary to identify a Facebook post -- divorced + from the various means and formats of receiving it -- and is therefore + sufficient as a URN. (The full URL by which this resource *might* be + retrieved, `https://www.facebook.com/10206332515695927`, is *not* an + appropriate URN; such an endpoint URL may be constructed by any process with + knowledge about Facebook posts, given its needs.) + + Rather than point directly to its ClientContent, a ContentVehicle may + optionally point to another ContentVehicle, regardless of when and with + what intent each was created -- (here differing from a "campaign") -- via + `intermediate_vehicle`; however, a ContentVehicle should never be made the + intermediate for another ContentVehicle which serves differing + ClientContent! To do so would make a liar out of the latter + ContentVehicle's (or "linking vehicles"'s) `client_content`. + + See the instance method `linkable_vehicles`, which returns a QuerySet of + ContentVehicles serving the same ClientContent as the instance. + `ContentVehicle.save()` further validates that any intermediate vehicle + serve matching client content, (though, because this constraint is not + enforceable by the database, it may be circumvented). - related_name = 'content_vehicle_campaigns' + """ + related_name = 'content_vehicles' - content_vehicle_campaign_id = models.AutoField(primary_key=True) + content_vehicle_id = models.AutoField(primary_key=True) urn = models.CharField("Vehicle Resource Name", max_length=30) # indexed below - content_vehicle = models.ForeignKey('magnus.ContentVehicle', related_name=related_name) - campaign = models.ForeignKey('magnus.Campaign', related_name=related_name) + content_vehicle_type = models.ForeignKey('magnus.ContentVehicleType', + related_name=related_name) + intermediate_vehicle = models.ForeignKey('self', blank=True, null=True, + related_name='linking_vehicles') + client_content = models.ForeignKey('magnus.ClientContent', + related_name=related_name) class Meta(base.BaseModel.Meta): - db_table = 'content_vehicle_campaigns' - unique_together = ('urn', 'content_vehicle') + db_table = 'content_vehicles' + unique_together = ('urn', 'content_vehicle_type') + + def linkable_vehicles(self): + return self._default_manager.filter(client_content_id=self.client_content_id) + + def save(self, *args, **kws): + intermediate = self.intermediate_vehicle + if intermediate and intermediate.client_content_id != self.client_content_id: + raise exceptions.ValidationError( + "intermediate vehicle's ClientContent must match that of the linking vehicle" + ) + return super(ContentVehicle, self).save(*args, **kws) def __unicode__(self): return self.urn @@ -238,24 +272,11 @@ def __unicode__(self): def __repr__(self): return u"<{}: {} [{}]>".format(self.__class__.__name__, self.urn, - self.content_vehicle.codename) - - -class Campaign(base.BaseModel): - - campaign_id = models.AutoField(primary_key=True) - name = models.CharField('Campaign Name', max_length=255) - client_content = models.ForeignKey('magnus.ClientContent', related_name='campaigns') - - class Meta(base.BaseModel.Meta): - db_table = 'campaigns' - - def __unicode__(self): - return self.name + self.content_vehicle_type.codename) class Client(base.BaseModel): - """A business client, for whom we engage FBAppUsers, through Campaigns.""" + """A business client, for whom we engage FBAppUsers, through ContentVehicles.""" client_id = models.AutoField(primary_key=True) name = models.CharField('Client Name', max_length=255) @@ -289,7 +310,7 @@ def __unicode__(self): return self.name def __repr__(self): - return "<{}: {}>".format(self.__class__.__name__, self.codename) + return u"<{}: {}>".format(self.__class__.__name__, self.codename) class ClientFBAppUser(base.BaseModel): @@ -325,7 +346,7 @@ class Event(base.BaseModel): event_id = fields.BigSerialField(primary_key=True) visit = fields.FlexibleForeignKey('magnus.Visit', related_name='events') event_type = models.CharField('Event Type', max_length=64) - campaign = models.ForeignKey('magnus.Campaign', null=True, related_name='events') + content_vehicle = models.ForeignKey('magnus.ContentVehicle', null=True, related_name='events') event_datetime = models.DateTimeField(db_index=True, default=timezone.now) data = fields.JSONField()