class Food(models.Model): name = models.CharField(max_length=255) related = RelatedObjectsDescriptor() related_beverages = RelatedObjectsDescriptor(RelatedBeverage, 'food', 'beverage') def __unicode__(self): return self.name
class Person(models.Model): name = models.CharField(max_length=255) related = RelatedObjectsDescriptor() def __unicode__(self): return self.name
class ReportedIssue(models.Model): created = models.DateField(auto_now_add=True) # Each reported issue needs a status. These are defined in the helpers class. status = models.PositiveIntegerField(max_length=1, choices=STATUS_CHOICES, default=1) # Holds a generic link to a reportable object content_type = models.ForeignKey(ContentType, verbose_name="Type of Job") object_id = models.PositiveIntegerField() content_object = generic.GenericForeignKey() #reportable = models.ForeignKey(Reportable) related = RelatedObjectsDescriptor() def __unicode__(self): return "%s: %s" % (self.content_object.__class__.__name__, self.content_object) # Override the save method to perform tasks on status change def save(self, *args, **kwargs): # Only applicable when editing the model if self.pk: old_instance = type(self).objects.get(pk=self.pk) if not old_instance.status == self.status: update_reports_status.delay(self) send_status_mail.delay(self) super(ReportedIssue, self).save(*args, **kwargs) class Meta: abstract = True
class ModelGroup(models.Model): name = models.CharField(max_length=100) related = RelatedObjectsDescriptor() def __unicode__(self): return self.name
class Symptom(models.Model): name = models.CharField(max_length=255) slug = AutoSlugField(populate_from="name", max_length=255) selfhacked_link = models.CharField(max_length=255, blank=True) related_objects = RelatedObjectsDescriptor() def __str__(self): return self.name
class FacilityIssue(ReportedIssue): # Holds a foreign key link to the facility in question facility = models.ForeignKey(Facility) # Enables auction to override cost cost = models.DecimalField(max_digits=9, decimal_places=2, help_text='The initial cost of the issue') # To prevent concurrent editing, needs a locked field locked = models.BooleanField() related = RelatedObjectsDescriptor()
class Document(models.Model): file = models.FileField(storage=fs, upload_to=UPLOAD_TO) related = RelatedObjectsDescriptor() def __unicode__(self): return self.file.name[len(UPLOAD_TO) + 1:] def get_download_url(self): return urlresolvers.reverse('documents_document_download', args=(self.pk, ))
class TestModel(models.Model): name = models.CharField(max_length=200) test = RelatedObjectsDescriptor() for_inline = models.ForeignKey('self', null=True, blank=True, related_name='inline_test_models') def __str__(self): return self.name
class GmtmModel(models.Model): name = models.CharField(max_length=200) relation = RelatedObjectsDescriptor() noise = models.ForeignKey('FkModel', null=True, blank=True) for_inline = models.ForeignKey('self', null=True, blank=True, related_name='inline') def __str__(self): return self.name
class VoteMixin(models.Model): """ Mixin providing a 'through' m2m relationship and helper fields and methods to inheriting classes, allowing easy use of the vote app """ votes = RelatedObjectsDescriptor(Vote) class Meta: abstract = True @property def upvotes(self): return self.votes.filter(is_upvote=True) @property def downvotes(self): return self.votes.filter(is_upvote=False) @property def vote_count(self): return self.upvotes.count() - self.downvotes.count() def voteup(self, user): return self._create_or_toggle_vote(user, True) def votedown(self, user): return self._create_or_toggle_vote(user, False) def get_vote_for_user(self, user): try: user_type = ContentType.objects.get(app_label="auth", model="user") vote = self.votes.get(object_id=user.pk, object_type=user_type) return vote except: return None def _create_or_toggle_vote(self, user, upvote): existing_vote = self.get_vote_for_user(user) if existing_vote is None: return self._create_vote(user, upvote) else: if existing_vote.is_upvote is None: existing_vote.is_upvote = upvote else: existing_vote.is_upvote = None existing_vote.save() return existing_vote def _create_vote(self, user, upvote): return self.votes.connect(user, is_upvote=upvote)
class Report(models.Model): created = models.DateField(auto_now_add=True) modified = models.DateField(auto_now=True) user = models.ForeignKey(User) description = models.CharField(max_length=255) status = models.DecimalField(max_digits=5, default=0, decimal_places=2) # The report needs to hold all the issues, and each issue may be in a different report. # For this reason, we need a generic many-to-many field #issues = models.ManyToManyField(ReportedIssue) issues = RelatedObjectsDescriptor() def __unicode__(self): return "Report %d" % self.pk
class FullModel(models.Model): name = models.CharField(max_length=200) oto = models.OneToOneField('self', related_name='reverse_oto') fk = models.ForeignKey('self', related_name='reverse_fk') mtm = models.ManyToManyField('self', related_name='reverse_mtm') content_type = models.ForeignKey(ContentType) object_id = models.PositiveIntegerField() gfk = GenericForeignKey("content_type", "object_id") if RelatedObjectsDescriptor: gmtm = RelatedObjectsDescriptor() def __str__(self): return self.name
class ContactContainer(CompanyTitleMixin): wizcard = models.ForeignKey(WizcardBase, related_name="contact_container") phone = TruncatingCharField(max_length=20, blank=True) media = RelatedObjectsDescriptor() def __unicode__(self): return (u'%(user)s\'s contact container: %(title)s@ %(company)s \n') % \ {'user': unicode(self.wizcard.user), 'title': unicode(self.title), 'company': unicode(self.company)} class Meta: ordering = ['id'] def get_fbizcard_url(self): bz = [x.media_element for x in self.media.all().generic_objects() if x.media_sub_type == MediaMixin.SUB_TYPE_F_BIZCARD] if bz: return bz return ""
class FireDepartment(RecentlyUpdatedMixin, models.Model): """ Models Fire Departments. """ DEPARTMENT_TYPE_CHOICES = [ ('Volunteer', 'Volunteer'), ('Mostly Volunteer', 'Mostly Volunteer'), ('Career', 'Career'), ('Mostly Career', 'Mostly Career'), ] REGION_CHOICES = [ ('Northeast', 'Northeast'), ('West', 'West'), ('South', 'South'), ('Midwest', 'Midwest'), (None, '') ] POPULATION_CLASSES = [ (0, 'Population less than 2,500.'), (1, 'Population between 2,500 and 4,999.'), (2, 'Population between 5,000 and 9,999.'), (3, 'Population between 10,000 and 24,999.'), (4, 'Population between 25,000 and 49,999.'), (5, 'Population between 50,000 and 99,999.'), (6, 'Population between 100,000 and 249,999.'), (7, 'Population between 250,000 and 499,999.'), (8, 'Population between 500,000 and 999,999.'), (9, 'Population greater than 1,000,000.'), ] created = models.DateTimeField(auto_now=True) modified = models.DateTimeField(auto_now=True) fdid = models.CharField(max_length=10) name = models.CharField(max_length=100) headquarters_address = models.ForeignKey(Address, null=True, blank=True, related_name='firedepartment_headquarters') mail_address = models.ForeignKey(Address, null=True, blank=True) headquarters_phone = PhoneNumberField(null=True, blank=True) headquarters_fax = PhoneNumberField(null=True, blank=True) department_type = models.CharField(max_length=20, choices=DEPARTMENT_TYPE_CHOICES, null=True, blank=True) organization_type = models.CharField(max_length=75, null=True, blank=True) website = models.URLField(null=True, blank=True) state = models.CharField(max_length=2) region = models.CharField(max_length=20, choices=REGION_CHOICES, null=True, blank=True) geom = models.MultiPolygonField(null=True, blank=True) objects = CalculationManager() priority_departments = PriorityDepartmentsManager() dist_model_score = models.FloatField(null=True, blank=True, editable=False, db_index=True) risk_model_deaths = models.FloatField(null=True, blank=True, db_index=True, verbose_name='Predicted deaths per year.') risk_model_injuries = models.FloatField(null=True, blank=True, db_index=True, verbose_name='Predicted injuries per year.') risk_model_fires = models.FloatField(null=True, blank=True, db_index=True, verbose_name='Predicted number of fires per year.') risk_model_fires_size0 = models.FloatField(null=True, blank=True, db_index=True, verbose_name='Predicted number of size 0 fires.') risk_model_fires_size0_percentage = models.FloatField(null=True, blank=True, verbose_name='Percentage of size 0 fires.') risk_model_fires_size1 = models.FloatField(null=True, blank=True, db_index=True, verbose_name='Predicted number of size 1 fires.') risk_model_fires_size1_percentage = models.FloatField(null=True, blank=True, verbose_name='Percentage of size 1 fires.') risk_model_fires_size2 = models.FloatField(null=True, blank=True, db_index=True, verbose_name='Predicted number of size 2 firese.') risk_model_fires_size2_percentage = models.FloatField(null=True, blank=True, verbose_name='Percentage of size 2 fires.') government_unit = RelatedObjectsDescriptor() population = models.IntegerField(null=True, blank=True) population_class = models.IntegerField(null=True, blank=True, choices=POPULATION_CLASSES) featured = models.BooleanField(default=False, db_index=True) class Meta: ordering = ('name',) index_together = [ ['population', 'id', 'region'], ['population', 'region'] ] @property def headquarters_geom(self): return getattr(self.headquarters_address, 'geom', None) @property def predicted_fires_sum(self): """ Convenience method to sum """ if self.risk_model_fires_size0 is None and self.risk_model_fires_size1 is None \ and self.risk_model_fires_size2 is None: return return (self.risk_model_fires_size0 or 0) + (self.risk_model_fires_size1 or 0) + \ (self.risk_model_fires_size2 or 0) @property def size2_and_greater_sum(self): """ Convenience method to sum """ if self.risk_model_fires_size1 is None and self.risk_model_fires_size2 is None: return return (self.risk_model_fires_size1 or 0) + (self.risk_model_fires_size2 or 0) @property def size2_and_greater_percentile_sum(self): """ Convenience method to sum """ if self.risk_model_fires_size1_percentage is None and self.risk_model_fires_size2_percentage is None: return return (self.risk_model_fires_size1_percentage or 0) + (self.risk_model_fires_size2_percentage or 0) @property def deaths_and_injuries_sum(self): if self.risk_model_deaths is None and self.risk_model_injuries is None: return return (self.risk_model_deaths or 0) + (self.risk_model_injuries or 0) @cached_property def population_class_stats(self): """ Returns summary statistics for calculation fields in the same population class. """ if not self.population_class: return [] cache_key = 'population_class_{0}_stats'.format(self.population_class) cached = cache.get(cache_key) if cached: return cached fields = ['dist_model_score', 'risk_model_fires', 'risk_model_deaths_injuries_sum', 'risk_model_size1_percent_size2_percent_sum', 'residential_fires_avg_3_years'] aggs = [] for field in fields: aggs.append(Min(field)) aggs.append(Max(field)) aggs.append(Avg(field)) results = self.population_metrics_table.objects.filter(population_class=self.population_class).aggregate(*aggs) cache.set(cache_key, results, timeout=60 * 60 * 24) return results def report_card_scores(self): if not self.population_metrics_table: return row = self.population_metrics_row from .managers import Ntile from django.db.models import When, Case, Q qs = self.population_metrics_table.objects.filter(risk_model_fires_quartile=row.risk_model_fires_quartile) qs = qs.annotate(**{'dist_quartile': Case(When(**{'dist_model_score__isnull': False, 'then': Ntile(4, output_field=models.IntegerField(), order_by='dist_model_score')}), output_field=models.IntegerField(), default=None)}) qs = qs.annotate(**{'size2_plus_quartile': Case(When(**{'risk_model_size1_percent_size2_percent_sum_quartile__isnull': False, 'then': Ntile(4, output_field=models.IntegerField(), order_by='risk_model_size1_percent_size2_percent_sum_quartile')}), output_field=models.IntegerField(), default=None)}) qs = qs.annotate(**{'deaths_injuries_quartile': Case(When(**{'risk_model_deaths_injuries_sum_quartile__isnull': False, 'then': Ntile(4, output_field=models.IntegerField(), order_by='risk_model_deaths_injuries_sum_quartile')}), output_field=models.IntegerField(), default=None)}) qs = qs.filter(id=self.id) return qs @property def population_metrics_table(self): """ Returns the appropriate population metrics table for this object. """ try: return get_model('firestation.PopulationClass{0}Quartile'.format(self.population_class)) except LookupError: return None @cached_property def population_metrics_row(self): """ Returns the matching row from the population metrics table. """ population_table = self.population_metrics_table if not population_table: return return population_table.objects.get(id=self.id) @property def government_unit_objects(self): """ Memoize the government_unit generic key lookup. """ if not getattr(self, '_government_unit_objects', None): self._government_unit_objects = self.government_unit.all().generic_objects() return self._government_unit_objects @property def fips(self): objs = self.government_unit_objects if objs: return [obj.fips for obj in objs if hasattr(obj, 'fips')] return [] @property def geom_area(self): """ Project the department's geometry into north america lambert conformal conic Returns km2 """ if self.geom: try: return self.geom.transform(102009, clone=True).area / 1000000 except: return @cached_property def nfirs_deaths_and_injuries_sum(self): return self.nfirsstatistic_set.filter(Q(metric='civilian_casualties') | Q(metric='firefighter_casualties'), fire_department=self, year__gte=2010).aggregate(Avg('count')) @cached_property def residential_fires_3_year_avg(self): if self.population_metrics_row: return self.population_metrics_row.residential_fires_avg_3_years return self.nfirsstatistic_set.filter(fire_department=self, metric='residential_structure_fires', year__gte=2010).aggregate(Avg('count')) def get_population_class(self): """ Returns the population class of a department based on NFPA community sizes categories as an integer. 9: > 1,000,000 8: (500000, 999999) 7: (250000, 499999) 6: (100000, 249999) 5: (50000, 99999) 4: (25000, 49999) 3: (10000, 24999) 2: (5000, 9999) 1: (2500, 4999) 0: < 2500 """ if self.population is None: return if self.population < 2500: return 0 if self.population >= 1000000: return 9 community_sizes = [ (500000, 999999), (250000, 499999), (100000, 249999), (50000, 99999), (25000, 49999), (10000, 24999), (5000, 9999), (2500, 4999)] for clazz, min_max in zip(reversed(range(1, 9)), community_sizes): if min_max[0] <= self.population <= min_max[1]: return clazz @property def similar_departments(self, ignore_regions_min=1000000): """ Identifies similar departments based on the protected population size and region. """ params = {} if self.population >= 1000000: params['population__gte'] = 1000000 elif self.population < 2500: params['population__lt'] = 2500 else: community_sizes = [ (500000, 999999), (250000, 499999), (100000, 249999), (50000, 99999), (25000, 49999), (10000, 24999), (5000, 9999), (2500, 4999)] for lower_bound, upper_bound in community_sizes: if lower_bound <= self.population <= upper_bound: params['population__lte'] = upper_bound params['population__gte'] = lower_bound break similar = FireDepartment.objects.filter(**params)\ .exclude(id=self.id)\ .extra(select={'difference': "abs(population - %s)"}, select_params=[self.population])\ .extra(order_by=['difference']) # Large departments may not have similar departments in their region. if self.population < ignore_regions_min: similar = similar.filter(region=self.region) return similar @property def thumbnail_name(self): return slugify(' '.join(['us', self.state, self.name])) + '.jpg' @property def thumbnail_name_no_marker(self): return slugify(' '.join(['us', self.state, self.name, 'no marker'])) + '.jpg' @property def thumbnail(self): return 'https://s3.amazonaws.com/vida-static/department-thumbnails/{0}'.format(self.thumbnail_name) @property def thumbnail_no_marker(self): return 'https://s3.amazonaws.com/vida-static/department-thumbnails/{0}' \ .format(self.thumbnail_name_no_marker) def generate_thumbnail(self, marker=True): geom = None if self.geom: geom = self.geom.centroid elif self.headquarters_address and self.headquarters_address.geom: geom = self.headquarters_address.geom else: return '/static/firestation/theme/assets/images/content/property-1.jpg' if marker and geom: marker = 'pin-l-embassy+0074D9({geom.x},{geom.y})/'.format(geom=geom) return 'http://api.tiles.mapbox.com/v4/garnertb.mmlochkh/{marker}' \ '{geom.x},{geom.y},8/500x300.png?access_token={access_token}'.format(marker=marker, geom=geom, access_token=getattr(settings, 'MAPBOX_ACCESS_TOKEN', '')) def set_geometry_from_government_unit(self): objs = self.government_unit_objects if objs: self.geom = MultiPolygon([obj.geom for obj in objs if getattr(obj, 'geom', None)]) self.save() def set_population_from_government_unit(self): """ Stores the population of government units on the FD object to speed up querying. """ objs = self.government_unit_objects if objs: for gov_unit in objs: pop = getattr(gov_unit, 'population', None) if pop is not None: if self.population is None: self.population = 0 self.population += pop else: self.population = None self.save() @classmethod def get_histogram(cls, field, bins=400): hist = histogram(list(cls.objects.filter(**{'{0}__isnull'.format(field): False}) .values_list(field, flat=True)), bins=bins) return json.dumps(zip(hist[1], hist[0]), separators=(',', ':')) def set_region(self, region): validate_choice(FireDepartment.REGION_CHOICES)(region) self.region = region self.save() @cached_property def description(self): """ A text description of the department used for displaying on the client side. """ try: name = self.name if not self.name.lower().endswith('department') and not self.name.lower().endswith('district'): name += ' fire department' return "The {name} is a {department_type} department located in the {object.region} NFPA region and headquartered in " \ "{object.headquarters_address.city}, {object.headquarters_address.state_province}."\ .format(name=name, department_type=self.department_type.lower(), object=self).strip() except: return 'No description for Fire Department' def residential_structure_fire_counts(self): return self.nfirsstatistic_set.filter(metric='residential_structure_fires')\ .extra(select={ 'year_max': 'SELECT MAX(COUNT) FROM firestation_nfirsstatistic b WHERE b.year = firestation_nfirsstatistic.year and b.metric=firestation_nfirsstatistic.metric' })\ .extra(select={ 'year_min': 'SELECT MIN(COUNT) FROM firestation_nfirsstatistic b WHERE b.year = firestation_nfirsstatistic.year and b.metric=firestation_nfirsstatistic.metric' }) @classmethod def load_from_usfa_csv(cls): """ Loads Fire Departments from http://apps.usfa.fema.gov/census-download. """ us, _ = Country.objects.get_or_create(name='United States of America', iso_code='US') with open(os.path.join(os.path.dirname(__file__), 'scripts/usfa-census-national.csv'), 'r') as csvfile: # This only runs once, since there isn't a good key to identify duplicates if not cls.objects.all().count(): reader = csv.DictReader(csvfile) counter = 0 for row in reader: # only run once. hq_address_params = {} hq_address_params['address_line1'] = row.get('HQ Addr1') hq_address_params['address_line2'] = row.get('HQ Addr2') hq_address_params['city'] = row.get('HQ City') hq_address_params['state_province'] = row.get('HQ State') hq_address_params['postal_code'] = row.get('HQ Zip') hq_address_params['country'] = us headquarters_address, _ = Address.objects.get_or_create(**hq_address_params) headquarters_address.save() mail_address_params = {} mail_address_params['address_line1'] = row.get('Mail Addr1') mail_address_params['address_line2'] = row.get('Mail Addr2') or row.get('Mail PO Box') mail_address_params['city'] = row.get('Mail City') mail_address_params['state_province'] = row.get('Mail State') mail_address_params['postal_code'] = row.get('Mail Zip') mail_address_params['country'] = us mail_address, _ = Address.objects.get_or_create(**mail_address_params) mail_address.save() params = {} params['fdid'] = row.get('FDID') params['name'] = row.get('Fire Dept Name') params['headquarters_phone'] = row.get('HQ Phone') params['headquarters_fax'] = row.get('HQ Fax') params['department_type'] = row.get('Dept Type') params['organization_type'] = row.get('Organization Type') params['website'] = row.get('Website') params['headquarters_address'] = headquarters_address params['mail_address'] = mail_address params['state'] = row.get('HQ State') cls.objects.create(**params) counter += 1 assert counter == cls.objects.all().count() @cached_property def slug(self): return slugify(self.name) def get_absolute_url(self): return reverse('firedepartment_detail_slug', kwargs=dict(pk=self.id, slug=self.slug)) def find_jurisdiction(self): from vida.usgs.models import CountyorEquivalent, IncorporatedPlace, UnincorporatedPlace counties = CountyorEquivalent.objects.filter(state_name='Virginia') for county in counties: incorporated = IncorporatedPlace.objects.filter(geom__intersects=county.geom) unincoporated = UnincorporatedPlace.objects.filter(geom__intersects=county.geom) station = FireStation.objects.filter(geom__intersects=county.geom) print 'County', county.name print 'Incorporated Place', incorporated.count() print 'Unincorporated Place', unincoporated.count() print 'Stations:', station def __unicode__(self): return self.name
class BaseEntityComponent(PolymorphicModel): EVENT = 'EVT' CAMPAIGN = 'CMP' TABLE = 'TBL' WIZCARD = 'WZC' SPEAKER = 'SPK' SPONSOR = 'SPN' MEDIA = 'MED' ATTENDEE_INVITEE = 'ATI' EXHIBITOR_INVITEE = 'EXI' COOWNER = 'COW' AGENDA = 'AGN' AGENDA_ITEM = 'AGI' POLL = 'POL' BADGE_TEMPLATE = 'BDG' SCANNED_USER = '******' CATEGORY = 'CAT' SERIALIZER_L0 = 0 SERIALIZER_L1 = 1 SERIALIZER_L2 = 2 SERIALIZER_FULL = 3 ENTITY_CHOICES = ((EVENT, 'Event'), (CAMPAIGN, 'Campaign'), (TABLE, 'Table'), (WIZCARD, 'Wizcard'), (SPEAKER, 'Speaker'), (SPONSOR, 'Sponsor'), (COOWNER, 'Coowner'), (ATTENDEE_INVITEE, 'AttendeeInvitee'), (EXHIBITOR_INVITEE, 'ExhibitorInvitee'), (MEDIA, 'Media'), (COOWNER, 'Coowner'), (AGENDA, 'Agenda'), (AGENDA_ITEM, 'AgendaItem'), (POLL, 'Polls'), (BADGE_TEMPLATE, 'Badges'), (SCANNED_USER, 'Scans'), (CATEGORY, 'Category')) SUB_ENTITY_CAMPAIGN = 'e_campaign' SUB_ENTITY_TABLE = 'e_table' SUB_ENTITY_WIZCARD = 'e_wizcard' SUB_ENTITY_SPEAKER = 'e_speaker' SUB_ENTITY_SPONSOR = 'e_sponsor' SUB_ENTITY_MEDIA = 'e_media' SUB_ENTITY_COOWNER = 'e_coowner' SUB_ENTITY_AGENDA = 'e_agenda' SUB_ENTITY_AGENDA_ITEM = 'e_agendaitem' SUB_ENTITY_POLL = 'e_poll' SUB_ENTITY_EXHIBITOR_INVITEE = 'e_exhibitor' SUB_ENTITY_ATTENDEE_INVITEE = 'e_attendee' SUB_ENTITY_BADGE_TEMPLATE = 'e_badge' SUB_ENTITY_SCANNED_USER = '******' SUB_ENTITY_CATEGORY = 'e_category' ENTITY_STATE_CREATED = "CRT" ENTITY_STATE_PUBLISHED = "PUB" ENTITY_STATE_EXPIRED = "EXP" ENTITY_STATE_DELETED = "DEL" ENTITY_STATE_DESTROY = "DES" ENTITY_STATE_CHOICES = ((ENTITY_STATE_CREATED, "Created"), (ENTITY_STATE_PUBLISHED, "Published"), (ENTITY_STATE_EXPIRED, "Expired"), (ENTITY_STATE_DELETED, "Deleted")) entity_types_mapping = { CAMPAIGN: SUB_ENTITY_CAMPAIGN, TABLE: SUB_ENTITY_TABLE, WIZCARD: SUB_ENTITY_WIZCARD, SPEAKER: SUB_ENTITY_SPEAKER, SPONSOR: SUB_ENTITY_SPONSOR, MEDIA: SUB_ENTITY_MEDIA, ATTENDEE_INVITEE: SUB_ENTITY_ATTENDEE_INVITEE, EXHIBITOR_INVITEE: EXHIBITOR_INVITEE, COOWNER: SUB_ENTITY_COOWNER, AGENDA: SUB_ENTITY_AGENDA, AGENDA_ITEM: SUB_ENTITY_AGENDA, POLL: SUB_ENTITY_POLL, BADGE_TEMPLATE: SUB_ENTITY_BADGE_TEMPLATE, SCANNED_USER: SUB_ENTITY_SCANNED_USER, CATEGORY: SUB_ENTITY_CATEGORY } # use this in overridden delete to know whether to remove or mark-deleted ENTITY_DELETE = 1 ENTITY_EXPIRE = 2 objects = BaseEntityComponentManager() entity_type = models.CharField(max_length=3, choices=ENTITY_CHOICES, default=EVENT) owners = models.ManyToManyField(User, through='BaseEntityComponentsOwner', related_name="owners_%(class)s_related") # sub-entities. using django-generic-m2m package related = RelatedObjectsDescriptor(WizcardRelatedField) engagements = models.OneToOneField( "EntityEngagementStats", null=True, related_name="engagements_%(class)s_related") tags = TaggableManager() entity_state = models.CharField(choices=ENTITY_STATE_CHOICES, default=ENTITY_STATE_CREATED, max_length=3) @classmethod def create(cls, e, owner, is_creator, **kwargs): obj = e.objects.create(**kwargs) # add owner BaseEntityComponentsOwner.objects.create(base_entity_component=obj, owner=owner, is_creator=is_creator) return obj @classmethod def my_entity_type(cls): return "" """ updates an existing entry, which was already created. This method may not really be required since creator is set during creation time itself """ @classmethod def update_creator(cls, obj, creator): BaseEntityComponentsOwner.objects.get( base_entity_component=obj, owner=creator).update(is_creator=True) @classmethod def add_owners(cls, obj, owners): for o in owners: BaseEntityComponentsOwner.objects.get_or_create( base_entity_component=obj, owner=o.user, defaults={'is_creator': False}) @classmethod def remove_owners(cls, obj, owners): for o in owners: BaseEntityComponentsOwner.objects.filter(base_entity_component=obj, owner=o.user).delete() @classmethod def entity_cls_from_type(cls, entity_type): from taganomy.models import Taganomy from entity.models import Event, Campaign, VirtualTable, \ Speaker, Sponsor, AttendeeInvitee, ExhibitorInvitee, CoOwners, Agenda, AgendaItem from media_components.models import MediaEntities from polls.models import Poll from scan.models import ScannedEntity, BadgeTemplate if entity_type == cls.EVENT: return Event elif entity_type == cls.CAMPAIGN: return Campaign elif entity_type == cls.TABLE: return VirtualTable elif entity_type == cls.ATTENDEE_INVITEE: return AttendeeInvitee elif entity_type == cls.EXHIBITOR_INVITEE: return ExhibitorInvitee elif entity_type == cls.MEDIA: return MediaEntities elif entity_type == cls.COOWNER: return CoOwners elif entity_type == cls.SPEAKER: return Speaker elif entity_type == cls.SPONSOR: return Sponsor elif entity_type == cls.MEDIA: return MediaEntities elif entity_type == cls.AGENDA: return Agenda elif entity_type == cls.AGENDA_ITEM: return AgendaItem elif entity_type == cls.POLL: return Poll elif entity_type == cls.BADGE_TEMPLATE: return BadgeTemplate elif entity_type == cls.SCANNED_USER: return ScannedEntity elif entity_type == cls.CATEGORY: return Taganomy else: raise RuntimeError('invalid entity_type: %s', entity_type) @classmethod def entity_ser_from_type_and_level(cls, entity_type, level=SERIALIZER_FULL): from entity.serializers import EventSerializerL2, EventSerializer, EventSerializerL0, EventSerializerL1, \ TableSerializerL1, TableSerializerL2, TableSerializer, \ CampaignSerializerL1, CampaignSerializer, CampaignSerializerL2, CoOwnersSerializer, \ SpeakerSerializerL2, SpeakerSerializer, SponsorSerializerL2, SponsorSerializerL1, SponsorSerializer, AttendeeInviteeSerializer, \ ExhibitorInviteeSerializer, AgendaSerializer, AgendaSerializerL1, AgendaItemSerializer, PollSerializer, PollSerializerL1 from scan.serializers import ScannedEntitySerializer, BadgeTemplateSerializer from entity.serializers import TaganomySerializer, TaganomySerializerL2 from media_components.serializers import MediaEntitiesSerializer ser_mapping = { cls.EVENT: { cls.SERIALIZER_L0: EventSerializerL0, cls.SERIALIZER_L1: EventSerializerL1, cls.SERIALIZER_L2: EventSerializerL2, cls.SERIALIZER_FULL: EventSerializer }, cls.CAMPAIGN: { cls.SERIALIZER_L0: CampaignSerializerL1, cls.SERIALIZER_L1: CampaignSerializerL1, cls.SERIALIZER_L2: CampaignSerializerL2, cls.SERIALIZER_FULL: CampaignSerializer }, cls.TABLE: { cls.SERIALIZER_L0: TableSerializerL1, cls.SERIALIZER_L1: TableSerializerL1, cls.SERIALIZER_L2: TableSerializerL2, cls.SERIALIZER_FULL: TableSerializer }, cls.ATTENDEE_INVITEE: { cls.SERIALIZER_FULL: AttendeeInviteeSerializer }, cls.EXHIBITOR_INVITEE: { cls.SERIALIZER_FULL: ExhibitorInviteeSerializer }, cls.MEDIA: { cls.SERIALIZER_L2: MediaEntitiesSerializer, cls.SERIALIZER_FULL: MediaEntitiesSerializer }, cls.COOWNER: { cls.SERIALIZER_FULL: CoOwnersSerializer }, cls.SPEAKER: { cls.SERIALIZER_L0: SpeakerSerializerL2, cls.SERIALIZER_L1: SpeakerSerializerL2, cls.SERIALIZER_L2: SpeakerSerializerL2, cls.SERIALIZER_FULL: SpeakerSerializer }, cls.SPONSOR: { cls.SERIALIZER_L0: SponsorSerializerL1, cls.SERIALIZER_L1: SponsorSerializerL1, cls.SERIALIZER_L2: SponsorSerializerL2, cls.SERIALIZER_FULL: SponsorSerializer }, cls.AGENDA: { cls.SERIALIZER_L0: AgendaSerializer, cls.SERIALIZER_L1: AgendaSerializerL1, cls.SERIALIZER_L2: AgendaSerializerL1, cls.SERIALIZER_FULL: AgendaSerializerL1, }, cls.AGENDA_ITEM: { cls.SERIALIZER_L0: AgendaItemSerializer, cls.SERIALIZER_L1: AgendaItemSerializer, cls.SERIALIZER_L2: AgendaItemSerializer, cls.SERIALIZER_FULL: AgendaItemSerializer }, cls.POLL: { cls.SERIALIZER_L0: PollSerializerL1, cls.SERIALIZER_L1: PollSerializerL1, cls.SERIALIZER_L2: PollSerializer, cls.SERIALIZER_FULL: PollSerializer }, cls.SCANNED_USER: { cls.SERIALIZER_FULL: ScannedEntitySerializer }, cls.CATEGORY: { cls.SERIALIZER_L0: TaganomySerializerL2, cls.SERIALIZER_L1: TaganomySerializerL2, cls.SERIALIZER_L2: TaganomySerializerL2, cls.SERIALIZER_FULL: TaganomySerializer }, cls.BADGE_TEMPLATE: { cls.SERIALIZER_FULL: BadgeTemplateSerializer } } return ser_mapping[entity_type][level] @classmethod def entity_cls_ser_from_type_level(cls, entity_type=None, level=SERIALIZER_FULL): c = cls.entity_cls_from_type(entity_type) s = cls.entity_ser_from_type_and_level(entity_type, level) return c, s @classmethod def content_type_from_entity_type(cls, entity_type): c = BaseEntityComponent.entity_cls_from_type(entity_type=entity_type) return ContentType.objects.get_for_model(c) @classmethod def entity_type_from_content_type(cls, c_type): _cls = c_type.model_class() return _cls.my_entity_type() @classmethod def sub_entity_type_from_entity_type(cls, entity_type): return cls.entity_types_mapping[entity_type] @classmethod def entity_cls_from_subentity_type(cls, entity_type): from entity.models import Campaign, VirtualTable, \ Speaker, Sponsor, CoOwners, Agenda, ExhibitorInvitee, AttendeeInvitee from taganomy.models import Taganomy from media_components.models import MediaEntities from wizcardship.models import Wizcard from polls.models import Poll from scan.models import ScannedEntity, BadgeTemplate if entity_type == cls.SUB_ENTITY_CAMPAIGN: c = Campaign elif type == cls.SUB_ENTITY_TABLE: c = VirtualTable elif entity_type == cls.SUB_ENTITY_WIZCARD: c = Wizcard elif entity_type == cls.SUB_ENTITY_SPEAKER: c = Speaker elif entity_type == cls.SUB_ENTITY_SPONSOR: c = Sponsor elif entity_type == cls.SUB_ENTITY_MEDIA: c = MediaEntities elif entity_type == cls.SUB_ENTITY_COOWNER: c = CoOwners elif entity_type == cls.SUB_ENTITY_AGENDA: c = Agenda elif entity_type == cls.SUB_ENTITY_POLL: c = Poll elif entity_type == cls.SUB_ENTITY_EXHIBITOR_INVITEE: c = ExhibitorInvitee elif entity_type == cls.SUB_ENTITY_ATTENDEE_INVITEE: c = AttendeeInvitee elif entity_type == cls.SUB_ENTITY_SCANNED_USER: c = ScannedEntity elif entity_type == cls.SUB_ENTITY_BADGE_TEMPLATE: c = BadgeTemplate elif entity_type == cls.SUB_ENTITY_CATEGORY: c = Taganomy else: raise AssertionError("Invalid sub_entity %s" % entity_type) return c def has_join_table_row(self, sub_entity): return self.get_sub_entities_gfk_of_type( object_id=sub_entity.id, alias=BaseEntityComponent.sub_entity_type_from_entity_type( sub_entity.entity_type)).exists() # gets the join table row between parent<->sub_entity. def get_join_table_row(self, sub_entity): if self.has_join_table_row(sub_entity): return self.get_sub_entities_gfk_of_type( object_id=sub_entity.id, alias=BaseEntityComponent.sub_entity_type_from_entity_type( sub_entity.entity_type)).get() return BaseEntityComponent.objects.none() # this does not send notif def add_subentities(self, ids, type): if not ids: return [] c = self.entity_cls_from_subentity_type(type) int_ids = map(lambda x: int(x), ids) objs = c.objects.filter(id__in=int_ids) for obj in objs: self.related.connect(obj, alias=type) return objs # this does not send notif def remove_subentities(self, ids, type): if not ids: return int_ids = map(lambda x: int(x), ids) self.related.filter(object_id__in=int_ids, alias=type).delete() # kind of tagonomy's pattern. add any new ones, remove those not in the list # this does not send notif def add_remove_sub_entities_of_type(self, ids, type): to_be_deleted_ids_qs = self.related.filter(alias=type) new_obj_ids = [] for id in ids: if not to_be_deleted_ids_qs.filter(object_id=id).exists(): new_obj_ids.append(id) to_be_deleted_ids_qs = to_be_deleted_ids_qs.exclude(object_id=id) # add the new ones self.add_subentities(new_obj_ids, type) # delete the rest to_be_deleted_ids_qs.delete() def add_subentity_obj(self, obj, alias, **kwargs): join_fields = kwargs.pop('join_fields', None) connection = self.related.connect(obj, alias=alias) if join_fields: connection.join_fields = join_fields connection.save() kwargs.update(notif_operation=verbs.NOTIF_OPERATION_CREATE) return connection, obj.post_connect_remove(self, **kwargs) def remove_sub_entity_obj(self, obj, subentity_type, **kwargs): self.related.filter(object_id=obj.id, alias=subentity_type).delete() kwargs.update(notif_operation=verbs.NOTIF_OPERATION_DELETE) return obj.post_connect_remove(self, **kwargs) def get_sub_entities_gfk_of_type(self, **kwargs): return self.related.filter(**kwargs) def get_sub_entities_of_type(self, entity_type, **kwargs): exclude = kwargs.pop('exclude', [self.ENTITY_STATE_DELETED]) subent = self.related.filter(alias=entity_type).generic_objects() return [se for se in subent if se.entity_state not in exclude] def get_sub_entities_id_of_type(self, entity_type, **kwargs): # Ideally we could have avoided the 2 iterations, but this is to ensure that the logic is consistent across 2 functions subent = self.get_sub_entities_of_type(entity_type, **kwargs) return [se.id for se in subent] def get_media_filter(self, type, sub_type): media = self.get_sub_entities_of_type(BaseEntity.SUB_ENTITY_MEDIA) return [ m for m in media if m.media_type in type and m.media_sub_type in sub_type ] def get_parent_entities(self, **kwargs): exclude = kwargs.pop('exclude', [self.ENTITY_STATE_DELETED]) parents = self.related.related_to().generic_objects() return [p for p in parents if p.entity_state not in exclude] def get_parent_entities_by_contenttype_id(self, contenttype_id, **kwargs): exclude = kwargs.pop('exclude', [self.ENTITY_STATE_DELETED]) parents = self.related.related_to().filter( parent_type_id=contenttype_id).generic_objects() return [p for p in parents if p.entity_state not in exclude] # is the instance of the kind that has notifiable users @property def is_floodable(self): return False def update_state_upon_link_unlink(self): return False # applies to child not parent. ie, self is the child def can_destroy_when_linked(self): return False def get_creator(self): return BaseEntityComponentsOwner.objects.filter( base_entity_component=self, is_creator=True).get().owner.profile.user def is_creator(self, user): return bool(user == self.get_creator()) def is_owner(self, user): return user in self.owners.all() # when a sub-entity gets related, it might want to do things like sending notifications # override this in the derived classes to achieve the same def post_connect_remove(self, parent, **kwargs): notif_operation = kwargs.pop('notif_operation', verbs.NOTIF_OPERATION_CREATE) send_notif = kwargs.pop('send_notif', True) if self.update_state_upon_link_unlink(): entity_state = BaseEntityComponent.ENTITY_STATE_CREATED if notif_operation == verbs.NOTIF_OPERATION_DELETE \ else BaseEntityComponent.ENTITY_STATE_PUBLISHED self.set_entity_state(entity_state) if send_notif and parent.is_active(): notify.send( self.get_creator(), # recipient is dummy recipient=self.get_creator(), notif_tuple=verbs.WIZCARD_ENTITY_UPDATE, target=parent, action_object=self, notif_operation=notif_operation) return send_notif def add_tags(self, taglist): self.tags.add(*taglist) def get_tags(self): return self.tags.names() def update_tags(self, taglist): return self.tags.set(*taglist) def modified_since(self, timestamp): return True def set_entity_state(self, state): self.entity_state = state self.save() def is_active(self): return bool( self.entity_state == BaseEntityComponent.ENTITY_STATE_PUBLISHED) def is_expired(self): return bool( self.entity_state == BaseEntityComponent.ENTITY_STATE_EXPIRED) def is_deleted(self): return bool( self.entity_state == BaseEntityComponent.ENTITY_STATE_DELETED) # nothing here, should be overridden in derived classes def user_state(self, user): return "" def delete(self, *args, **kwargs): type = kwargs.pop("type", BaseEntityComponent.ENTITY_DELETE) if type == BaseEntityComponent.ENTITY_EXPIRE: self.set_entity_state(BaseEntityComponent.ENTITY_STATE_EXPIRED) elif type == BaseEntityComponent.ENTITY_DELETE: self.set_entity_state(BaseEntityComponent.ENTITY_STATE_DELETED) else: super(BaseEntityComponent, self).delete(*args, **kwargs) @property def push_name_str(self): entity_to_push_name_str = { self.EVENT: ' ', self.CAMPAIGN: ' Campaign ', self.TABLE: ' Table ', self.SPEAKER: ' Speaker ', self.SPONSOR: ' Sponsor ', self.MEDIA: ' Media ', self.AGENDA: ' Agenda ', self.POLL: ' Poll ', } return entity_to_push_name_str[ self. entity_type] if self.entity_type in entity_to_push_name_str else " "
class Wizcard(WizcardBase): # TODO move this upstairs user = models.OneToOneField(User, related_name='wizcard') wizconnections_to = models.ManyToManyField('self', through='WizConnectionRequest', symmetrical=False, related_name='wizconnections_from') media = RelatedObjectsDescriptor() objects = WizcardManager() class Meta: verbose_name = _(u'wizcard') verbose_name_plural = _(u'wizcards') def __unicode__(self): return _(u'%(user)s\'s wizcard') % {'user': unicode(self.user)} def wizconnection_count(self): return self.get_connections().count() wizconnection_count.short_description = _(u'Cards count') def wizconnection_summary(self, count=7): wizconnection_list = self.get_connections().all().select_related()[:count] return u'[%s%s]' % (u', '.join(unicode(f.user) for f in wizconnection_list), u', ...' if self.wizconnection_count() > count else u'') wizconnection_summary.short_description = _(u'Summary of wizconnections') def serialize_wizconnections(self): out = [] # doing import here to avoid circular import. This does affect performance # (eventhough django caches re-imports). Still, doing it here since this # method is called only in the resync path from wizcardship.serializers import WizcardSerializerL1, WizcardSerializerL2 admin = self.get_admin_wizcard() out.append(WizcardSerializerL2(admin, many=True, context={'user_state': verbs.ADMIN}).data) connected = self.get_connections_without_admin() if connected: out.append(WizcardSerializerL2(connected, many=True, context={'user_state': verbs.CONNECTED}).data) following = self.get_following_only() if following: out.append(WizcardSerializerL1(following, many=True, context={'user_state': verbs.FOLLOWED}).data) return out def flood(self): # Q two aync notifs, one for half and one each for full & half notify.send( self.user, # recipient is dummy at this stage recipient=self.user, notif_tuple=verbs.WIZCARD_UPDATE_HALF, target=self ) notify.send( self.user, # recipient is dummy at this stage recipient=self.user, notif_tuple=verbs.WIZCARD_UPDATE_FULL, target=self ) # typically kwargs can contain the notif tuple def flood_set(self, **kwargs): # full card for connections and half for followers fs_w = self.get_connections() if verbs.get_notif_type(kwargs.pop('ntuple')) == verbs.NOTIF_UPDATE_WIZCARD_F \ else self.get_followers_only() fs_u = [x.user for x in fs_w] return fs_u def check_flick_duplicates(self, lat, lng): if not settings.DO_FLICK_AGGLOMERATE: return None #check if nearby cards can be combined...do we need to adjust centroid and all that ? for w in self.flicked_cards.exclude(expired=True): if wizlib.haversine(w.lng, w.lat, lng, lat) < settings.WIZCARD_FLICK_AGGLOMERATE_RADIUS: return w return None def get_relationship(self, wizcard): try: return WizConnectionRequest.objects.get( from_wizcard=self, to_wizcard=wizcard) except: return None def add_relationship(self, wizcard, status=verbs.PENDING, ctx=""): rel = self.get_relationship(wizcard) if not rel: rel = WizConnectionRequest.objects.create( from_wizcard=self, to_wizcard=wizcard, cctx=ctx, status=status) else: rel.cctx=ctx rel.status=status rel.save() return rel def remove_relationship(self, wizcard): WizConnectionRequest.objects.filter( from_wizcard=self, to_wizcard=wizcard).delete() def set_delete_relationship(self, wizcard): WizConnectionRequest.objects.filter( from_wizcard=self, to_wizcard=wizcard).update(status=verbs.DELETED) # ME -> def get_connected_to(self, status): return self.wizconnections_to.filter( requests_to__status=status) # ME <- def get_connected_from(self, status): return self.wizconnections_from.filter( requests_from__status=status) # cards I have deleted def get_deleted(self): return self.wizconnections_from.filter( requests_from__status=verbs.DELETED ) #2 way connected... def get_connections(self): return self.wizconnections_to.filter( requests_to__status=verbs.ACCEPTED, requests_from__status=verbs.ACCEPTED, requests_from__to_wizcard=self ) #2 way connected + admin wizcard def get_connections_without_admin(self): return self.wizconnections_to.filter( Q(user__profile__is_admin=False) | Q(requests_to__status=verbs.ACCEPTED, requests_from__status=verbs.ACCEPTED, requests_from__to_wizcard=self ) ).distinct() def get_admin_wizcard(self): # ofcourse, optimal way is to simply look up via UserProfile, or # even cache the admin wizcard. importing UserProfile here is causing # circular import issue return self.wizconnections_to.filter(Q(user__profile__is_admin=True)) def get_pending_from(self): return self.get_connected_from(verbs.PENDING) # those having my card (=my flood list) # wrapper around get_connected_to def get_followers(self): return self.get_connected_to(verbs.ACCEPTED) #my rolodex def get_following(self): return self.get_connected_from(verbs.ACCEPTED) # exclude connected def get_followers_only(self): return self.get_connected_to(verbs.ACCEPTED).exclude( id__in=Wizcard.objects.filter( requests_from__status=verbs.ACCEPTED, requests_from__to_wizcard=self)) # note: this excludes admin wizcard def get_following_only(self): return self.get_connected_from(verbs.ACCEPTED).exclude( id__in=Wizcard.objects.filter( Q(requests_to__status=verbs.ACCEPTED, requests_to__from_wizcard=self)| Q(user__profile__is_admin=True))) def get_following_no_admin(self): return self.get_connected_from(verbs.ACCEPTED).exclude( id__in=Wizcard.objects.filter(Q(user__profile__is_admin=True)))
class WizcardBase(PolymorphicModel, Base413Mixin): sms_url = URLField(blank=True) media = RelatedObjectsDescriptor() def get_latest_company(self): qs = self.contact_container.all() if qs.exists(): return qs[0].company return None def get_latest_cc_fields(self, *args): cc = self.contact_container.all()[0] return attrgetter(*args)(cc) def is_admin_wizcard(self): return self.user.profile.is_admin def save_sms_url(self, url): self.sms_url = wizlib.shorten_url(url) self.save() def get_sms_url(self): return self.sms_url def get_thumbnail_url(self): tn = [x.media_element for x in self.media.all().generic_objects() if x.media_sub_type == MediaMixin.SUB_TYPE_THUMBNAIL] if tn: return tn return "" def save_vcard(self, vobj): self.vcard = vobj self.save() def get_name(self): return self.user.first_name + " " + self.user.last_name def get_video_url(self): vd = [(x.media_element, x.media_iframe) for x in self.media.all().generic_objects() if x.media_type == MediaMixin.TYPE_VIDEO] if vd: return vd return "" @property def get_ext_fields(self): return self.ext_fields @property def get_vcard(self): return self.vcard @property def get_email(self): return self.email def get_latest_title(self): qs = self.contact_container.all() if qs.exists(): return qs[0].title return None
class Note(models.Model): content = models.TextField() related = RelatedObjectsDescriptor(AnotherRelatedObject)
def monkey_patch(model_class, name='related', descriptor=None): rel_obj = descriptor or RelatedObjectsDescriptor() rel_obj.contribute_to_class(model_class, name) setattr(model_class, name, rel_obj) return True