class Household(BaseDispatchSyncUuidModel, BaseSyncUuidModel): """A system model that represents the household asset. See also HouseholdStructure.""" plot = models.ForeignKey(Plot, null=True) household_identifier = models.CharField( verbose_name='Household Identifier', max_length=25, unique=True, help_text="Household identifier", null=True, editable=False) household_sequence = models.IntegerField( editable=False, null=True, help_text=('is 1 for first household in plot, 2 for second, 3, etc. ' 'Embedded in household identifier.')) hh_int = models.IntegerField(null=True, editable=False, help_text='not used') hh_seed = models.IntegerField(null=True, editable=False, help_text='not used') report_datetime = models.DateTimeField(verbose_name='Report Date/Time', null=True) gps_degrees_s = EncryptedDecimalField(verbose_name='GPS Degrees-South', max_digits=10, null=True, decimal_places=0, editable=False, help_text='comes from plot') gps_minutes_s = EncryptedDecimalField(verbose_name='GPS Minutes-South', max_digits=10, null=True, decimal_places=4, editable=False, help_text='comes from plot') gps_degrees_e = EncryptedDecimalField(verbose_name='GPS Degrees-East', null=True, max_digits=10, decimal_places=0, editable=False, help_text='comes from plot') gps_minutes_e = EncryptedDecimalField(verbose_name='GPS Minutes-East', max_digits=10, null=True, decimal_places=4, editable=False, help_text='comes from plot') gps_lon = models.FloatField(verbose_name='longitude', null=True, editable=False, help_text='comes from plot') gps_lat = models.FloatField(verbose_name='latitude', null=True, editable=False, help_text='comes from plot') gps_target_lon = models.FloatField( verbose_name='target waypoint longitude', null=True, editable=False, help_text='comes from plot') gps_target_lat = models.FloatField(verbose_name='target waypoint latitude', null=True, editable=False, help_text='comes from plot') target_radius = models.FloatField(default=.025, help_text='km', editable=False) community = models.CharField( max_length=25, help_text= 'If the community is incorrect, please contact the DMC immediately.', null=True, editable=False) replaced_by = models.CharField( max_length=25, null=True, help_text= u'The identifier of the plot that this household is replaced by', editable=False) replaceable = models.NullBooleanField( verbose_name='Replaceable?', default=None, editable=False, help_text='Updated by replacement helper') comment = EncryptedTextField( max_length=250, help_text="You may provide a comment here or leave BLANK.", blank=True, null=True) uploaded_map = models.CharField(verbose_name="filename of uploaded map", max_length=25, null=True, blank=True) # not used!! action = models.CharField(max_length=25, null=True, default='unconfirmed', editable=False) # updated by subject_consent save method enrolled = models.BooleanField( default=False, editable=False, help_text=('Set to true if one member is consented. ' 'Updated by Household_structure post_save.')) enrolled_datetime = models.DateTimeField( null=True, editable=False, help_text=('datetime that household is enrolled. ' 'Updated by Household_structure post_save.')) history = AuditTrail() objects = HouseholdManager() def save(self, *args, **kwargs): using = kwargs.get('using') try: if self.__class__.objects.using(using).get(id=self.id).replaced_by: raise AlreadyReplaced('Household {0} has been replaced ' 'by plot {1}.'.format( self.household_identifier, self.replaced_by)) except self.__class__.DoesNotExist: pass self.allow_enrollment(using) return super(Household, self).save(*args, **kwargs) def get_identifier(self): return self.household_identifier def allow_enrollment(self, using, exception_cls=None, instance=None): """Raises an exception if the plot is not enrolled and BHS_FULL_ENROLLMENT_DATE is past.""" instance = instance or self return self.plot.allow_enrollment(using, exception_cls, plot_instance=instance.plot) @property def mapper_name(self): return self.community def __unicode__(self): if site_mappers.get_current_mapper( ).clinic_plot_identifier[0:6] == self.household_identifier[0:6]: return self.plot.description return self.household_identifier def natural_key(self): return (self.household_identifier, ) natural_key.dependencies = [ 'bcpp_household.household', ] def get_subject_identifier(self): return self.household_identifier def gps(self): return "S{0} {1} E{2} {3}".format(self.gps_degrees_s, self.gps_minutes_s, self.gps_degrees_e, self.gps_minutes_e) def dispatch_container_lookup(self, using=None): return (Plot, 'plot__plot_identifier') def bypass_for_edit_dispatched_as_item(self, using=None, update_fields=None): """Bypasses dispatched check if update_fields is set by the replacement_helper.""" try: if 'replaced_by' in update_fields: return True except TypeError as type_error: if '\'NoneType\' is not iterable' in str(type_error): pass return False def structure(self): return """<a href="{url}" />structure</a>""" # .format(url=url) structure.allow_tags = True def member_count(self): HouseholdMember = models.get_model('bcpp_household_member', 'HouseholdMember') return HouseholdMember.objects.filter( household_structure__household__pk=self.pk).count() def deserialize_prep(self, **kwargs): # Household being deleted by an IncommingTransaction, we go ahead and delete it. # An extra household created by mistake. if kwargs.get('action', None) and kwargs.get('action', None) == 'D': self.delete() class Meta: app_label = 'bcpp_household' ordering = [ '-household_identifier', ]
class PlotLogEntry(BaseDispatchSyncUuidModel, BaseSyncUuidModel): """A model completed by the user to track an RA\'s attempts to confirm a Plot.""" plot_log = models.ForeignKey(PlotLog) report_datetime = models.DateTimeField(verbose_name="Report date", validators=[ datetime_not_before_study_start, datetime_not_future, date_in_survey ]) log_status = models.CharField( verbose_name='What is the status of this plot?', max_length=25, choices=PLOT_LOG_STATUS) reason = models.CharField( verbose_name='If inaccessible, please indicate the reason.', max_length=25, blank=True, null=True, choices=INACCESSIBILITY_REASONS) reason_other = models.CharField(verbose_name='If Other, specify', max_length=100, blank=True, null=True) comment = EncryptedTextField( verbose_name="Comments", max_length=250, null=True, blank=True, help_text= ('IMPORTANT: Do not include any names or other personally identifying ' 'information in this comment')) history = AuditTrail() objects = PlotLogEntryManager() def save(self, *args, **kwargs): using = kwargs.get('using,') self.allow_enrollment(using) super(PlotLogEntry, self).save(*args, **kwargs) def natural_key(self): return (self.report_datetime, ) + self.plot_log.natural_key() def dispatch_container_lookup(self, using=None): return (Plot, 'plot_log__plot__plot_identifier') def allow_enrollment(self, using, exception_cls=None, instance=None): """Stops enrollments.""" instance = instance or self return self.plot_log.plot.allow_enrollment( using, exception_cls, plot_instance=instance.plot_log.plot) def __unicode__(self): return unicode(self.plot_log) + '(' + unicode( self.report_datetime) + ')' class Meta: app_label = 'bcpp_household' unique_together = ('plot_log', 'report_datetime')
class SubjectMoved(BaseMemberStatusModel): """A model completed by the user to indicate a subject has moved from the household and or community.""" moved_household = models.CharField( max_length=7, verbose_name= 'Has the participant moved out of the household where last seen', choices=YES_NO_UNKNOWN, null=True, blank=False, help_text="") moved_community = models.CharField( max_length=7, verbose_name='Has the participant moved out of the community', choices=YES_NO_UNKNOWN, null=True, blank=False, help_text="") new_community = models.CharField( max_length=50, verbose_name= 'If the participant has moved, provide the name of the new community', null=True, blank=True, help_text= "If moved out of the community, provide a new community name or \'UNKNOWN\'" ) update_locator = models.CharField( max_length=7, verbose_name='Has the locator information changed', choices=YES_NO_UNKNOWN, null=True, blank=False, help_text=('If YES, please enter the changed information ' 'the locator form')) comment = EncryptedTextField(verbose_name="Comment", max_length=250, blank=True, help_text=('')) history = AuditTrail() def save(self, *args, **kwargs): household = models.get_model( 'bcpp_household', 'Household').objects.get( household_identifier=self.household_member.household_structure. household.household_identifier) if household.replaced_by: raise AlreadyReplaced('Household {0} replaced.'.format( household.household_identifier)) self.survey = self.household_member.survey super(SubjectMoved, self).save(*args, **kwargs) class Meta: app_label = 'bcpp_household_member' verbose_name = "Subject Moved" verbose_name_plural = "Subject Moved" ordering = ['household_member']
class HouseholdLogEntry(BaseDispatchSyncUuidModel, BaseSyncUuidModel): """A model completed by the user each time the household is visited.""" household_log = models.ForeignKey(HouseholdLog) report_datetime = models.DateField( verbose_name="Report date", validators=[date_not_before_study_start, date_not_future]) household_status = models.CharField(verbose_name='Household Status', max_length=50, choices=HOUSEHOLD_LOG_STATUS, null=True, blank=False) next_appt_datetime = models.DateTimeField( verbose_name="Re-Visit On", help_text="The date and time to revisit household", null=True, blank=True) next_appt_datetime_source = models.CharField( verbose_name="Source", max_length=25, choices=NEXT_APPOINTMENT_SOURCE, help_text='source of information for the appointment date', null=True, blank=True) comment = EncryptedTextField(null=True, blank=True) history = AuditTrail() objects = HouseholdLogEntryManager() def natural_key(self): return (self.report_datetime, ) + self.household_log.natural_key() def save(self, *args, **kwargs): household = models.get_model( 'bcpp_household', 'Household').objects.get( household_identifier=self.household_log.household_structure. household.household_identifier) if household.replaced_by: raise AlreadyReplaced('Household {0} replaced.'.format( household.household_identifier)) super(HouseholdLogEntry, self).save(*args, **kwargs) def dispatch_container_lookup(self, using=None): return ( Plot, 'household_log__household_structure__household__plot__plot_identifier' ) def __unicode__(self): household_log = self.household_log or None return unicode(household_log) + '(' + unicode( self.report_datetime) + ')' class Meta: app_label = 'bcpp_household' unique_together = ('household_log', 'report_datetime')
class Plot(BaseDispatchSyncUuidModel, BaseSyncUuidModel): """A model completed by the user (and initially by the system) to represent a Plot in the community.""" plot_identifier = models.CharField(verbose_name='Plot Identifier', max_length=25, unique=True, help_text="Plot identifier", editable=False, db_index=True) eligible_members = models.IntegerField( verbose_name="Approximate number of age eligible members", blank=True, null=True, help_text=(("Provide an approximation of the number of people " "who live in this residence who are age eligible."))) description = EncryptedTextField( verbose_name="Description of plot/residence", max_length=250, blank=True, null=True) comment = EncryptedTextField(verbose_name="Comment", max_length=250, blank=True, null=True) cso_number = EncryptedCharField( verbose_name="CSO Number", blank=True, null=True, db_index=True, help_text=("provide the CSO number or leave BLANK.")) household_count = models.IntegerField( verbose_name="Number of Households on this plot.", default=0, null=True, validators=[MaxValueValidator(9)], help_text=("Provide the number of households in this plot.")) time_of_week = models.CharField( verbose_name= 'Time of week when most of the eligible members will be available', max_length=25, choices=TIME_OF_WEEK, blank=True, null=True) time_of_day = models.CharField( verbose_name= 'Time of day when most of the eligible members will be available', max_length=25, choices=TIME_OF_DAY, blank=True, null=True) gps_degrees_s = EncryptedDecimalField(verbose_name='GPS Degrees-South', max_digits=10, null=True, decimal_places=0) gps_minutes_s = EncryptedDecimalField(verbose_name='GPS Minutes-South', max_digits=10, null=True, decimal_places=4) gps_degrees_e = EncryptedDecimalField(verbose_name='GPS Degrees-East', null=True, max_digits=10, decimal_places=0) gps_minutes_e = EncryptedDecimalField(verbose_name='GPS Minutes-East', max_digits=10, null=True, decimal_places=4) gps_lon = EncryptedDecimalField(verbose_name='longitude', max_digits=10, null=True, decimal_places=6) gps_lat = EncryptedDecimalField(verbose_name='latitude', max_digits=10, null=True, decimal_places=6) gps_target_lon = EncryptedDecimalField( verbose_name='target waypoint longitude', max_digits=10, null=True, decimal_places=6) gps_target_lat = EncryptedDecimalField( verbose_name='target waypoint latitude', max_digits=10, null=True, decimal_places=6) status = models.CharField(verbose_name='Plot status', max_length=35, null=True, choices=PLOT_STATUS) target_radius = models.FloatField(default=.025, help_text='km', editable=False) distance_from_target = models.FloatField(null=True, editable=True, help_text='distance in meters') # 20 percent plots is reperesented by 1 and 5 percent of by 2, the rest of # the plots which is 75 percent selected value is None selected = models.CharField(max_length=25, null=True, verbose_name='selected', choices=SELECTED, editable=False) device_id = models.CharField(max_length=2, null=True, editable=False) action = models.CharField(max_length=25, null=True, default=UNCONFIRMED, editable=False) access_attempts = models.IntegerField( default=0, editable=False, help_text= 'Number of attempts to access a plot to determine it\'s status.') # Google map static images for this plots with different zoom levels. # uploaded_map_16, uploaded_map_17, uploaded_map_18 zoom level 16, 17, 18 respectively uploaded_map_16 = models.CharField( verbose_name="Map image at zoom level 16", max_length=25, null=True, blank=True, editable=False) uploaded_map_17 = models.CharField( verbose_name="Map image at zoom level 17", max_length=25, null=True, blank=True, editable=False) uploaded_map_18 = models.CharField( verbose_name="Map image at zoom level 18", max_length=25, null=True, blank=True, editable=False) community = models.CharField( max_length=25, help_text= 'If the community is incorrect, please contact the DMC immediately.', validators=[ is_valid_community, ], editable=False) section = models.CharField(max_length=25, null=True, verbose_name='Section', editable=False) sub_section = models.CharField(max_length=25, null=True, verbose_name='Sub-section', help_text=u'', editable=False) bhs = models.NullBooleanField( default=None, editable=False, help_text=('True indicates that plot is enrolled into BHS. ' 'Updated by bcpp_subject.subject_consent post_save')) enrolled_datetime = models.DateTimeField( null=True, editable=False, help_text=('datetime that plot is enrolled into BHS. ' 'Updated by bcpp_subject.subject_consent post_save')) htc = models.NullBooleanField(default=False, editable=False) replaces = models.CharField( max_length=25, null=True, blank=True, editable=False, help_text=("plot or household identifier that this plot replaces.")) replaced_by = models.CharField( max_length=25, null=True, blank=True, editable=False, help_text=u'The identifier of the plot that this plot was replaced by') replaceable = models.NullBooleanField( verbose_name='Replaceable?', default=None, editable=False, help_text='Updated by replacement helper') history = AuditTrail() objects = PlotManager() def __unicode__(self): if site_mappers.get_current_mapper( ).clinic_plot_identifier == self.plot_identifier: return 'BCPP-CLINIC' else: return self.plot_identifier def natural_key(self): return (self.plot_identifier, ) def save(self, *args, **kwargs): using = kwargs.get('using') update_fields = kwargs.get('update_fields') if not self.plot_identifier == site_mappers.get_current_mapper( ).clinic_plot_identifier: self.allow_enrollment(using, update_fields=update_fields) if self.replaced_by and update_fields != ['replaced_by', 'htc']: raise AlreadyReplaced( 'Plot {0} is no longer part of BHS. It has been replaced ' 'by plot {1}.'.format(self.plot_identifier, self.replaced_by)) if not self.community: # plot data is imported and not entered, so community must be provided on import raise ValidationError( 'Attribute \'community\' may not be None for model {0}'.format( self)) if self.household_count > settings.MAX_HOUSEHOLDS_PER_PLOT: raise ValidationError('Number of households cannot exceed {}. ' 'Perhaps catch this in the forms.py. See ' 'settings.MAX_HOUSEHOLDS_PER_PLOT'.format( settings.MAX_HOUSEHOLDS_PER_PLOT)) # unless overridden, if self.community != to mapper.map_area, raise self.verify_plot_community_with_current_mapper(self.community) # if self.community does not get valid mapper, will raise an error that should be caught in forms.pyx mapper_cls = site_mappers.registry.get(self.community) mapper = mapper_cls() if not self.plot_identifier: self.plot_identifier = PlotIdentifier(mapper.map_code, using).get_identifier() if not self.plot_identifier: raise IdentifierError( 'Expected a value for plot_identifier. Got None') # if user added/updated gps_degrees_[es] and gps_minutes_[es], update gps_lat, gps_lon if (self.gps_degrees_e and self.gps_degrees_s and self.gps_minutes_e and self.gps_minutes_s): self.gps_lat = mapper.get_gps_lat(self.gps_degrees_s, self.gps_minutes_s) self.gps_lon = mapper.get_gps_lon(self.gps_degrees_e, self.gps_minutes_e) mapper.verify_gps_location(self.gps_lat, self.gps_lon, MapperError) mapper.verify_gps_to_target( self.gps_lat, self.gps_lon, self.gps_target_lat, self.gps_target_lon, self.target_radius, MapperError, radius_bypass_instance=self.increase_radius_instance) self.distance_from_target = mapper.gps_distance_between_points( self.gps_lat, self.gps_lon, self.gps_target_lat, self.gps_target_lon) * 1000 self.action = self.get_action() try: update_fields = update_fields + [ 'action', 'distance_from_target', 'plot_identifier', 'user_modified' ] kwargs.update({'update_fields': update_fields}) except TypeError: pass super(Plot, self).save(*args, **kwargs) def get_identifier(self): return self.plot_identifier @property def identifier_segment(self): return self.plot_identifier[:-3] @property def increase_radius_instance(self): IncreasePlotRadius = models.get_model('bcpp_household', 'IncreasePlotRadius') try: return IncreasePlotRadius.objects.get(plot=self) except IncreasePlotRadius.DoesNotExist: return None def create_household(self, count, instance=None, using=None): instance = instance or self using = using or 'default' if instance.pk: for _ in range(0, count): Household = models.get_model('bcpp_household', 'Household') Household.objects.create( **{ 'plot': instance, 'gps_target_lat': instance.gps_target_lat, 'gps_target_lon': instance.gps_target_lon, 'gps_lat': instance.gps_lat, 'gps_lon': instance.gps_lon, 'gps_degrees_e': instance.gps_degrees_e, 'gps_degrees_s': instance.gps_degrees_s, 'gps_minutes_e': instance.gps_minutes_e, 'gps_minutes_s': instance.gps_minutes_s }) def allow_enrollment(self, using, exception_cls=None, plot_instance=None, update_fields=None): """Raises an exception if an attempt is made to edit a plot after the BHS_FULL_ENROLLMENT_DATE or if the plot has been allocated to HTC.""" plot_instance = plot_instance or self using = using or 'default' update_fields = update_fields or [] exception_cls = exception_cls or ValidationError if using == 'default': # do not check on remote systems mapper_instance = site_mappers.get_current_mapper() if plot_instance.id: if plot_instance.htc and 'htc' not in update_fields: raise exception_cls( 'Modifications not allowed, this plot has been assigned to the HTC campaign.' ) if settings.ALLOW_ENROLLMENT: if not mapper_instance.map_code == '00' and not plot_instance.bhs and date.today( ) > mapper_instance.current_survey_dates.full_enrollment_date: raise exception_cls( 'BHS enrollment for {0} ended on {1}. This plot, and the ' 'data related to it, may not be modified. ' 'See site_mappers'.format( self.community, mapper_instance. current_survey_dates.full_enrollment_date)) return True def safe_delete_households(self, existing_no, instance=None, using=None): """ Deletes households and HouseholdStructure if member_count==0 and no log entry. If there is a household log entry, this DOES NOT delete the household """ Household = models.get_model('bcpp_household', 'Household') HouseholdStructure = models.get_model('bcpp_household', 'HouseholdStructure') HouseholdLog = models.get_model('bcpp_household', 'HouseholdLog') HouseholdLogEntry = models.get_model('bcpp_household', 'HouseholdLogEntry') instance = instance or self using = using or 'default' def household_valid_to_delete(instance): """ Checks whether there is a plot log entry for each log. If it does not exists the household can be deleted. """ allowed_to_delete = [] for hh in Household.objects.using(using).filter(plot=instance): if (HouseholdLogEntry.objects.filter( household_log__household_structure__household=hh). exists() or hh in allowed_to_delete): continue allowed_to_delete.append(hh) return allowed_to_delete def delete_confirmed_household(instance, existing_no): """ Deletes required number of households. """ def validate_number_to_delete(instance, existing_no): if (existing_no in [0, 1] or instance.household_count == existing_no or instance.household_count > existing_no or instance.household_count == 0): return False else: if household_valid_to_delete(instance): del_valid = existing_no - instance.household_count if len(household_valid_to_delete( instance)) < del_valid: return len(household_valid_to_delete(instance)) else: return del_valid return False def delete_households_for_non_residential(instance, existing_no): household_to_delete = household_valid_to_delete(instance) try: if len(household_to_delete ) == existing_no and existing_no > 0: hh = HouseholdStructure.objects.filter( household__in=household_to_delete) hl = HouseholdLog.objects.filter( household_structure__in=hh) hl.delete() # delete household_logs hh.delete() # delete household_structure for hh in household_to_delete: with transaction.atomic(): Household.objects.get( id=hh.id).delete() # delete household return True else: return False except IntegrityError: return False except DatabaseError: return False def delete_household(instance, existing_no): try: delete_no = validate_number_to_delete( instance, existing_no) if validate_number_to_delete( instance, existing_no) else 0 if not delete_no == 0: deletes = household_valid_to_delete( instance)[:delete_no] hh = HouseholdStructure.objects.filter( household__in=deletes) hl = HouseholdLog.objects.filter( household_structure__in=hh) hl.delete() # delete household_logs hh.delete() # delete household_structure for hh in deletes: with transaction.atomic(): Household.objects.get( id=hh.id).delete() # delete household return True else: return False except IntegrityError: return False except DatabaseError: return False if instance.status in [RESIDENTIAL_NOT_HABITABLE, NON_RESIDENTIAL]: return delete_households_for_non_residential( instance, existing_no) else: return delete_household(instance, existing_no) return delete_confirmed_household(instance, existing_no) def create_or_delete_households(self, instance=None, using=None): """Creates or deletes households to try to equal the number of households reported on the plot instance. This gets called by a household post_save signal and on the plot save method on change. * If number is greater than actual household instances, households are created. * If number is less than actual household instances, households are deleted as long as there are no household members and the household log does not have entries. * bcpp_clinic is a special case to allow for a plot to represent the BCPP Clinic.""" instance = instance or self using = using or 'default' Household = models.get_model('bcpp_household', 'Household') # check that tuple has not changed and has "residential_habitable" if instance.status: if instance.status not in [ item[0] for item in list(PLOT_STATUS) + [('bcpp_clinic', 'BCPP Clinic')] ]: raise AttributeError( '{0} not found in choices tuple PLOT_STATUS. {1}'.format( instance.status, PLOT_STATUS)) existing_household_count = Household.objects.using(using).filter( plot__pk=instance.pk).count() instance.create_household(instance.household_count - existing_household_count, using=using) instance.safe_delete_households(existing_household_count, using=using) with transaction.atomic(): count = Household.objects.using(using).filter( plot__pk=instance.pk).count() return count def get_action(self): retval = UNCONFIRMED if self.gps_lon and self.gps_lat: retval = CONFIRMED return retval @property def validate_plot_accessible(self): if self.plot_log_entry and ( self.plot_inaccessible is False) and self.plot_log_entry.log_status == ACCESSIBLE: return True return False def gps(self): return "S{0} {1} E{2} {3}".format(self.gps_degrees_s, self.gps_minutes_s, self.gps_degrees_e, self.gps_minutes_e) def is_dispatch_container_model(self): return True def dispatched_as_container_identifier_attr(self): return 'plot_identifier' def dispatch_container_lookup(self): return (self.__class__, 'plot_identifier') def include_for_dispatch(self): return True def bypass_for_edit_dispatched_as_item(self, using=None, update_fields=None): """Bypasses dispatched check if update_fields is set by the replacement_helper.""" if update_fields: if 'replaces' in update_fields or 'htc' in update_fields or 'replaced_by' in update_fields: return True return False def get_contained_households(self): from bhp066.apps.bcpp_household.models import Household households = Household.objects.filter( plot__plot_identifier=self.plot_identifier) return households @property def log_form_label(self): # TODO: where is this used? using = 'default' PlotLog = models.get_model('bcpp_household', 'PlotLog') PlotLogEntry = models.get_model('bcpp_household', 'PlotLogEntry') form_label = [] try: plot_log = PlotLog.objects.using(using).get(plot=self) for plot_log_entry in PlotLogEntry.objects.using(using).filter( plot_log=plot_log).order_by('report_datetime'): try: form_label.append( (plot_log_entry.log_status.lower() + '-' + plot_log_entry.report_datetime.strftime('%Y-%m-%d'), plot_log_entry.id)) except AttributeError: # log_status is None ?? form_label.append( (plot_log_entry.report_datetime.strftime('%Y-%m-%d'), plot_log_entry.id)) except PlotLog.DoesNotExist: pass if self.access_attempts < 3 and self.action != CONFIRMED: form_label.append(('add new entry', 'add new entry')) if not form_label and self.action != CONFIRMED: form_label.append(('add new entry', 'add new entry')) return form_label @property def log_entry_form_urls(self): # TODO: where is this used? # TODO: this does not belong on the plot model! """Returns a url or urls to the plotlogentry(s) if an instance(s) exists.""" using = 'default' PlotLog = models.get_model('bcpp_household', 'PlotLog') PlotLogEntry = models.get_model('bcpp_household', 'PlotLogEntry') entry_urls = {} try: plot_log = PlotLog.objects.using(using).get(plot=self) for entry in PlotLogEntry.objects.using(using).filter( plot_log=plot_log).order_by('report_datetime'): entry_urls[entry.pk] = self._get_form_url( 'plotlogentry', entry.pk) add_url_2 = self._get_form_url('plotlogentry', model_pk=None, add_url=True) entry_urls['add new entry'] = add_url_2 except PlotLog.DoesNotExist: pass return entry_urls def _get_form_url(self, model, model_pk=None, add_url=None): url = '' pk = None app_label = 'bcpp_household' if add_url: url = reverse('admin:{0}_{1}_add'.format(app_label, model)) return url if not model_pk: # This is a like a SubjectAbsentee model_class = models.get_model(app_label, model) try: pk = model_class.objects.get(plot=self).pk except model_class.DoesNotExist: pk = None else: pk = model_pk if pk: url = reverse('admin:{0}_{1}_change'.format(app_label, model), args=(pk, )) else: url = reverse('admin:{0}_{1}_add'.format(app_label, model)) return url @property def location(self): if self.plot_identifier.endswith('0000-00'): return 'clinic' else: return 'household' @property def plot_inaccessible(self): """Returns True if the plot is inaccessible as defined by its status and number of attempts.""" PlotLogEntry = models.get_model('bcpp_household', 'plotlogentry') return PlotLogEntry.objects.filter( plot_log__plot__id=self.id, log_status=INACCESSIBLE).count() >= 3 @property def target_radius_in_meters(self): return self.target_radius * 1000 @property def increase_plot_radius(self): """Returns an instance of IncreasePlotRadius if the user should be allowed to change the target_radius otherwise returns None. Plot must be inaccessible and the last reason (of 3) be either "dogs" or "locked gate" """ PlotLogEntry = models.get_model('bcpp_household', 'PlotLogEntry') IncreasePlotRadius = models.get_model('bcpp_household', 'IncreasePlotRadius') created = False increase_plot_radius = None try: increase_plot_radius = IncreasePlotRadius.objects.get(plot=self) except IncreasePlotRadius.DoesNotExist: if self.plot_inaccessible: plot_log_entries = PlotLogEntry.objects.filter( plot_log__plot__id=self.id).order_by('report_datetime') if plot_log_entries[2].reason in ['dogs', 'locked_gate']: increase_plot_radius = IncreasePlotRadius.objects.create( plot=self) created = True except IndexError: pass return increase_plot_radius, created def verify_plot_community_with_current_mapper(self, community, exception_cls=None): """Returns True if the plot.community = the current mapper.map_area. This check can be disabled using the settings attribute VERIFY_PLOT_COMMUNITY_WITH_CURRENT_MAPPER. """ verify_plot_community_with_current_mapper = True # default exception_cls = exception_cls or ValidationError try: verify_plot_community_with_current_mapper = settings.VERIFY_PLOT_COMMUNITY_WITH_CURRENT_MAPPER except AttributeError: pass if verify_plot_community_with_current_mapper: if community != site_mappers.get_current_mapper().map_area: raise exception_cls( 'Plot community does not correspond with the current mapper ' 'community of \'{}\'. Got \'{}\'. ' 'See settings.VERIFY_PLOT_COMMUNITY_WITH_CURRENT_MAPPER'. format(site_mappers.get_current_mapper().map_area, community)) @property def plot_log(self): """Returns an instance of the plot log.""" PlotLog = models.get_model('bcpp_household', 'plotlog') try: instance = PlotLog.objects.get(plot__id=self.id) except PlotLog.DoesNotExist: instance = None return instance @property def plot_log_entry(self): PlotLogEntry = models.get_model('bcpp_household', 'plotlogentry') try: return PlotLogEntry.objects.filter( plot_log__plot__id=self.id).latest('report_datetime') except PlotLogEntry.DoesNotExist: print "PlotLogEntry.DoesNotExist" class Meta: app_label = 'bcpp_household' ordering = [ '-plot_identifier', ] unique_together = (('gps_target_lat', 'gps_target_lon'), )