def __init__(self, *args, **kwargs): super(LocationField, self).__init__(*args, **kwargs) kwargs.pop('based_fields', None) kwargs.pop('zoom', None) kwargs.pop('suffix', None) PointField.__init__(self, *args, **kwargs)
class UserProfile(models.Model, PictureMixin): PATIENT = 'MP' CONNOISSEUR = 'CN' BUSINESS = 'BS' DOCTOR = 'DR' DISPENSARY = 'DS' USER_CHOICES = ( (PATIENT, 'Medical Patient'), (CONNOISSEUR, 'Connoisseur'), (BUSINESS, 'Business'), (DOCTOR, 'Doctor'), (DISPENSARY, 'Dispensary'), ) SINGLE = 'SI' MARRIED = 'MA' DATING = 'DA' LOOKING = 'JL' COMPLICATED = 'IC' ENGAGED = 'EN' SEPARATED = 'SE' WIDOWED = 'WI' REL_STATUS_CHOICES = ( (SINGLE, 'Single'), (MARRIED, 'Married'), (DATING, 'In Relationship'), (LOOKING, 'Just Looking'), (ENGAGED, 'Engaged'), (SEPARATED, 'Separated'), (WIDOWED, 'Widowed'), ) MALE = 'MA' FEMALE = 'FM' BI = 'BI' GENDER_CHOICES = ( (MALE, 'Male'), (FEMALE, 'Female'), ) INT_CHOICES = ( (MALE, 'Male'), (FEMALE, 'Female'), (BI, 'Bi-Sexual'), ) user = models.OneToOneField(User, related_name='profile') profile_image = models.ForeignKey(UploadedFile, blank=True, null=True, default='', on_delete=models.SET_NULL, related_name='profile_image') cover_image = models.ForeignKey(UploadedFile, blank=True, null=True, default='', on_delete=models.SET_NULL, related_name='cover_image') about_me = models.TextField(null=True, blank=True) date_of_birth = models.DateField(blank=True, null=False, default=timezone_now()) user_type = models.CharField(max_length=2, choices=USER_CHOICES, default=PATIENT) rel_status = models.CharField(max_length=2, choices=REL_STATUS_CHOICES, default=SINGLE) gender = models.CharField(max_length=2, choices=GENDER_CHOICES, default=FEMALE) address = models.CharField(max_length=200, default="") country = models.CharField(choices=COUNTRIES, verbose_name='Country', max_length=50, default=CANADA) state = models.CharField(choices=STATES, verbose_name='State', max_length=50, blank=True, null=True, default=None) post_code = models.CharField(verbose_name="Postal Code", max_length=12, default="") website = models.URLField("Website", blank=True) company = models.CharField(max_length=50, blank=True) has_accepted_tos = models.BooleanField(default=False, verbose_name='Accept site terms') is_18_or_older = models.BooleanField( default=False, verbose_name='I am at least 18 years old') is_private = models.BooleanField('Privacy', default=False) location = PointField(srid=4326, null=True, blank=True) interested_in = models.CharField(max_length=2, choices=INT_CHOICES, default=BI) maxdistance = models.SmallIntegerField( validators=[MaxValueValidator(2500), MinValueValidator(1)], blank=True, default=250) def __str__(self): return "%s's profile" % self.user class Meta: db_table = 'user_profile' verbose_name = 'user profile' verbose_name_plural = 'user profiles' def profile_image_url(self): """ Return the URL for the user's Facebook icon if the user is logged in via Facebook, otherwise return the user's Gravatar URL """ fb_uid = SocialAccount.objects.filter(user_id=self.user.id, provider='facebook') if len(fb_uid): return "http://graph.facebook.com/{}/picture?width=125&height=125".format( fb_uid[0].uid) elif self.user.profile.profile_image is not None: return self.user.profile.profile_image.file.url else: return "http://www.gravatar.com/avatar/{}?s=125".format( hashlib.md5(self.user.email.encode('utf-8')).hexdigest()) def cover_image_url(self): """ Return the URL for the user's Facebook icon if the user is logged in via Facebook, otherwise return the user's Gravatar URL """ if self.user.profile.cover_image is not None: return self.user.profile.cover_image.file.url else: return "http://420withme.com/media/users/cover/none.jpg" def account_verified(self): """ If the user is logged in and has verified hisser email address, return True, otherwise return False """ result = EmailAddress.objects.filter(email=self.user.email) if len(result): return result[0].verified return False
class Report(models.Model): created_at = models.DateTimeField(auto_now_add=True) update_at = models.DateTimeField(auto_now=True) first_symptomatic = models.DateField(default=datetime.now) location = PointField(null=True, blank=True)
class Site(IrekuaModelBaseUser): help_text = _(''' Site Model A site consists of the specification of coordinates. The datum assumed is WGS-84. A name for the site can be specified for easier future retrieval. Also an optional locality field is added to locate the site within a larger area and provide hierarchical organization of sites. The creator of the site is registered so that users can search within their previously created sites when setting up a new monitoring event. ''') name = models.CharField( max_length=128, db_column='name', verbose_name=_('name'), help_text=_('Name of site (visible only to owner)'), blank=True, null=True) locality = models.CharField( max_length=256, db_column='locality', verbose_name=_('locality'), help_text=_('Name of locality in which the site is located'), blank=True) geo_ref = PointField( blank=True, db_column='geo_ref', verbose_name=_('geo ref'), help_text=_('Georeference of site as Geometry'), spatial_index=True) latitude = models.FloatField( db_column='latitude', verbose_name=_('latitude'), help_text=_('Latitude of site (in decimal degrees)'), validators=[MinValueValidator(-90), MaxValueValidator(90)], blank=True) longitude = models.FloatField( db_column='longitude', verbose_name=_('longitude'), help_text=_('Longitude of site (in decimal degrees)'), validators=[MinValueValidator(-180), MaxValueValidator(180)], blank=True) altitude = models.FloatField( blank=True, db_column='altitude', verbose_name=_('altitude'), help_text=_('Altitude of site (in meters)'), null=True) class Meta: verbose_name = _('Site') verbose_name_plural = _('Sites') ordering = ['-created_on'] def sync_coordinates_and_georef(self): if self.latitude is not None and self.longitude is not None: self.geo_ref = Point([self.longitude, self.latitude]) return if self.geo_ref: self.latitude = self.geo_ref.y self.longitude = self.geo_ref.x return msg = _('Geo reference or longitude-latitude must be provided') raise ValidationError({'geo_ref': msg}) def __str__(self): name = '' if self.name is not None: name = ': ' + self.name msg = _('Site %(id)s%(name)s') params = dict( id=self.id, name=name) return msg % params def clean(self): self.sync_coordinates_and_georef() super(Site, self).clean() def has_coordinate_permission(self, user): has_simple_permission = ( user.is_superuser | user.is_model | user.is_curator | (self.created_by == user) ) if has_simple_permission: return True collections = self.collection_set.all() for collection in collections.prefetch_related('collection_type'): collection_type = collection.collection_type queryset = collection_type.administrators.filter(id=user.id) if queryset.exists(): return True collection_users = CollectionUser.objects.filter( user=user.pk, collection__in=collections) if not collection_users.exists(): return False for collectionuser in collection_users.prefetch_related('role'): role = collectionuser.role queryset = role.permissions.filter(codename='view_collection_sites') if queryset.exists(): return True return False @property def items(self): return Item.objects.filter( sampling_event_device__sampling_event__collection_site__site=self) @property def sampling_events(self): return SamplingEvent.objects.filter( collection_site__site=self) @property def map_widget(self): name = 'point_{}'.format(self.pk) widget = IrekuaMapWidget(attrs={ 'map_width': '100%', 'map_height': '100%', 'id': name, 'disabled': True}) return widget.render(name, self.geo_ref) @property def map_widget_no_controls(self): name = 'point_{}'.format(self.pk) widget = IrekuaMapWidgetNoControls(attrs={ 'map_width': '100%', 'map_height': '100%', 'id': name, 'disabled': True}) return widget.render(name, self.geo_ref) @property def map_widget_obscured(self): name = 'point_{}'.format(self.pk) widget = IrekuaMapWidgetObscured(attrs={ 'map_width': '100%', 'map_height': '100%', 'id': name, 'disabled': True}) return widget.render(name, self.geo_ref)
class Device(models.Model): """ """ ''' Metadata ''' class Meta: app_label = 'foundation' db_table = 'mika_devices' verbose_name = _('Device') verbose_name_plural = _('Devices') default_permissions = () permissions = ( # ("can_get_opening_hours_specifications", "Can get opening hours specifications"), # ("can_get_opening_hours_specification", "Can get opening hours specifications"), # ("can_post_opening_hours_specification", "Can create opening hours specifications"), # ("can_put_opening_hours_specification", "Can update opening hours specifications"), # ("can_delete_opening_hours_specification", "Can delete opening hours specifications"), ) indexes = (BrinIndex( fields=['created_at', 'last_modified_at'], autosummarize=True, ), ) ''' Constants & Choices ''' class DEVICE_TYPE: LOGGER = 1 DEVICE_TYPE_OF_CHOICES = ((DEVICE_TYPE.LOGGER, _('Data Logger')), ) class DEVICE_STATE: NEW = 1 ONLINE = 2 OFFLINE = 3 ERROR = 4 ARCHIVED = 5 # A.k.a. "Deleted". DEVICE_STATE_CHOICES = ( (DEVICE_STATE.NEW, _('New')), (DEVICE_STATE.ONLINE, _('Online')), (DEVICE_STATE.OFFLINE, _('Offline')), (DEVICE_STATE.ERROR, _('Error')), (DEVICE_STATE.ARCHIVED, _('Archived')), ) ''' Object Managers ''' objects = DeviceManager() ''' Fields ''' # # Specific device fields. # id = models.BigAutoField( _("ID"), primary_key=True, help_text= _('The unique identifier used by us to identify a device in our system and we keep internal to our system (i.e. we do not release it to customer).' ), ) uuid = models.UUIDField( help_text= _('The unique identifier used by us to identify a device in our system and we release this value to the customer.' ), default=uuid.uuid4, null=False, editable=False, db_index=True, unique=True, ) user = models.ForeignKey("User", help_text=_('The user whom owns this device.'), blank=False, null=False, related_name="devices", on_delete=models.CASCADE) product = models.ForeignKey( "Product", help_text=_('The type of product this device is.'), blank=False, null=False, related_name="devices", on_delete=models.CASCADE) activated_at = models.DateTimeField( _("Activated At"), help_text= _('The datetime that this device first made an API call to our API web-service.' ), blank=True, null=True, editable=False, ) timezone = models.CharField(_("Timezone"), help_text=_('The timezone of the device.'), max_length=32, choices=TIMEZONE_CHOICES, default="UTC") invoice = models.ForeignKey( "Invoice", help_text=_('The e-commerce invoice this device is related to.'), blank=True, null=True, related_name="devices", on_delete=models.SET_NULL) slug = models.SlugField( _("Slug"), help_text= _('The unique slug used for this device when accessing device details page.' ), max_length=127, blank=True, null=False, db_index=True, unique=True, editable=False, ) version = models.PositiveSmallIntegerField( _("Version"), help_text= _('The version number this device is. This field controls what features a device has access to.' ), blank=True, null=False, default=1, ) power_consumption_in_kilowatts_per_hour = models.FloatField( _("Power consumption in kilowatts per hour"), help_text= _('The amount of energy consumed by this device, and all the attached instruments, per kilowatt hours.' ), blank=True, null=True, ) is_verified = models.BooleanField( _("Is verified"), help_text= _('Is this device verified by Mikaponics. Only devices built by Mikaponics can be verified.' ), default=False, blank=True, ) # # Real-time operation fields. # state = models.PositiveSmallIntegerField( _("State"), help_text=_('The state of device.'), blank=False, null=False, default=DEVICE_STATE.NEW, choices=DEVICE_STATE_CHOICES, ) last_measurement = models.ForeignKey( "TimeSeriesDatum", help_text=_('The latest measured reading by the device.'), related_name="+", on_delete=models.SET_NULL, blank=True, null=True, ) last_camera_snapshot = models.ForeignKey( "TimeSeriesImageDatum", help_text= _('The latest snapshot image capture by the camera instrument in the device. This field is only used by the camera instrument.' ), related_name="+", on_delete=models.SET_NULL, blank=True, null=True, ) # # Hardware Product Information # hardware_manufacturer = models.CharField( _("Hardware Manufacturer"), max_length=31, help_text= _('The manufacturer\'s name whom built the hardware that this device runs on. Ex: "Raspberry Pi Foundation".' ), blank=True, default="", null=True, ) hardware_product_name = models.CharField( _("Hardware Product Name"), max_length=31, help_text= _('The offical product name given by the manufacturer of the hardware that this device runs on. Ex: "Raspberry Pi 3 Model B+".' ), blank=True, default="", null=True, ) hardware_produt_id = models.CharField( _("Hardware Product ID"), max_length=31, help_text= _('The manufacturer\'s product ID of the hardware that this device runs on. Ex: "PI3P".' ), blank=True, default="", null=True, ) hardware_product_serial = models.CharField( _("Hardware Product Serial"), max_length=31, help_text= _('The serial number of the hardware that this device runs on. Ex: "0000000000000000".' ), blank=True, default="", ) # # https://schema.org/GeoCoordinates # # elevation = models.FloatField( _("Elevation"), help_text= _('The elevation of a location (<a href="https://en.wikipedia.org/wiki/World_Geodetic_System">WGS 84</a>).' ), blank=True, null=True) location = PointField( # Combine latitude and longitude into a single field. _("Location"), help_text= _('A longitude and latitude coordinates of this device. For example -81.245277,42.984924 (<a href="https://en.wikipedia.org/wiki/World_Geodetic_System">WGS 84</a>).' ), null=True, blank=True, srid=4326, db_index=True) #--------------------------# # https://schema.org/Place # #--------------------------# global_location_number = models.CharField( _("Global Location Number"), max_length=255, help_text= _('The <a href="https://www.gs1.org/standards/id-keys/gln">Global Location Number</a> (GLN, sometimes also referred to as International Location Number or ILN) of the respective organization, person, or place. The GLN is a 13-digit number used to identify parties and physical locations.' ), blank=True, null=True, ) #--------------------------# # https://schema.org/Thing # #--------------------------# name = models.CharField( _("Name"), max_length=255, help_text=_('The name of the device.'), blank=False, null=False, ) alternate_name = models.CharField( _("Alternate Name"), max_length=255, help_text=_('An alias for the device.'), blank=True, null=True, ) description = models.TextField( _("Description"), help_text=_('A description of the device.'), blank=False, null=True, default='', ) url = models.URLField(_("URL"), help_text=_('URL of the device.'), null=True, blank=True) image = models.ImageField(upload_to='devices/', help_text=_('An image of the device.'), null=True, blank=True) identifier = models.CharField( _("Identifier"), max_length=255, help_text= _('The identifier property represents any kind of identifier for any kind of <a href="https://schema.org/Thing">Thing</a>, such as ISBNs, GTIN codes, UUIDs etc. Schema.org provides dedicated properties for representing many of these, either as textual strings or as URL (URI) links. See <a href="https://schema.org/docs/datamodel.html#identifierBg">background notes</a> for more details.' ), blank=True, null=True, ) # # Audit details # created_at = models.DateTimeField(auto_now_add=True, db_index=True) created_by = models.ForeignKey( "User", help_text=_('The user whom created this device.'), related_name="created_devices", on_delete=models.SET_NULL, blank=True, null=True, editable=False, ) created_from = models.GenericIPAddressField( _("Created from IP"), help_text=_('The IP address of the creator.'), blank=True, null=True, editable=False, ) created_from_is_public = models.BooleanField( _("Is created from IP public?"), help_text=_('Is creator a public IP and is routable.'), default=False, blank=True, editable=False, ) last_modified_at = models.DateTimeField(auto_now=True) last_modified_by = models.ForeignKey( "User", help_text=_('The user whom last modified this device.'), related_name="last_modified_devices", on_delete=models.SET_NULL, blank=True, null=True, editable=False, ) last_modified_from = models.GenericIPAddressField( _("Last modified from IP"), help_text=_('The IP address of the modifier.'), blank=True, null=True, editable=False, ) last_modified_from_is_public = models.BooleanField( _("Is Last modified from IP public?"), help_text=_('Is modifier a public IP and is routable.'), default=False, blank=True, editable=False, ) def save(self, *args, **kwargs): """ Override the save function so we can add extra functionality. (1) If we created the object then we will generate a custom slug. (a) If user exists then generate slug based on user's name. (b) Else generate slug with random string. """ if not self.slug: # CASE 1 OF 2: HAS USER. if self.user: count = Device.objects.filter(user=self.user).count() count += 1 # Generate our slug. self.slug = slugify(self.user) + "-device-" + str(count) # If a unique slug was not found then we will keep searching # through the various slugs until a unique slug is found. while Device.objects.filter(slug=self.slug).exists(): self.slug = slugify(self.user) + "-device-" + str( count) + "-" + get_random_string(length=8) # CASE 2 OF 2: DOES NOT HAVE USER. else: self.slug = "device-" + get_random_string(length=32) super(Device, self).save(*args, **kwargs) def __str__(self): return self.slug def get_absolute_url(self): return "/device/" + str(self.slug) def get_pretty_state(self): result = dict(self.DEVICE_STATE_CHOICES).get(self.state) return str(result) def get_pretty_last_measured_value(self): if self.last_measured_value: return str(self.last_measured_value ) + " " + self.last_measured_unit_of_measure return _("No data available") def get_pretty_last_measured_at(self): if self.last_measured_at: return str(self.last_measured_at) return _("No data available") def invalidate(self, method_name): """ Function used to clear the cache for the cached property functions. """ try: if method_name == 'humidity_instrument': del self.humidity_instrument elif method_name == 'temperature_instrument': del self.temperature_instrument elif method_name == 'tvoc_instrument': del self.tvoc_instrument elif method_name == 'co2_instrument': del self.co2_instrument elif method_name == 'air_pressure_instrument': del self.air_pressure_instrument elif method_name == 'altitude_instrument': del self.altitude_instrument elif method_name == 'water_level_instrument': del self.water_level_instrument elif method_name == 'power_usage_instrument': del self.temperature_instrument elif method_name == 'ph_instrument': del self.temperature_instrument elif method_name == 'ec_instrument': del self.temperature_instrument elif method_name == 'orp_instrument': del self.temperature_instrument elif method_name == 'camera_instrument': del self.camera_instrument elif method_name == 'heat_vision_instrument': del self.heat_vision_instrument elif method_name == 'uv_light_instrument': del self.uv_light_instrument elif method_name == 'triad_spectroscopy_instrument': del self.triad_spectroscopy_instrument else: raise Exception("Method name not found.") except AttributeError: pass def invalidate_all(self): """ Function used to clear *all* the cache for the cached property functions. """ try: self.invalidate('humidity_instrument') self.invalidate('temperature_instrument') self.invalidate('tvoc_instrument') self.invalidate('co2_instrument') self.invalidate('air_pressure_instrument') self.invalidate('altitude_instrument') self.invalidate('water_level_instrument') self.invalidate('power_usage_instrument') self.invalidate('ph_instrument') self.invalidate('ec_instrument') self.invalidate('orp_instrument') self.invalidate('camera_instrument') self.invalidate('heat_vision_instrument') self.invalidate('uv_light_instrument') self.invalidate('triad_spectroscopy_instrument') except AttributeError: pass @cached_property def humidity_instrument(self): if self.instruments: from foundation.models.instrument import Instrument return self.instruments.filter( type_of=Instrument.INSTRUMENT_TYPE.HUMIDITY).first() return None @cached_property def temperature_instrument(self): if self.instruments: from foundation.models.instrument import Instrument return self.instruments.filter( type_of=Instrument.INSTRUMENT_TYPE.TEMPERATURE).first() return None @cached_property def tvoc_instrument(self): if self.instruments: from foundation.models.instrument import Instrument return self.instruments.filter( type_of=Instrument.INSTRUMENT_TYPE.TVOC).first() return None @cached_property def co2_instrument(self): if self.instruments: from foundation.models.instrument import Instrument return self.instruments.filter( type_of=Instrument.INSTRUMENT_TYPE.CO2).first() return None @cached_property def air_pressure_instrument(self): if self.instruments: from foundation.models.instrument import Instrument return self.instruments.filter( type_of=Instrument.INSTRUMENT_TYPE.AIR_PRESSURE).first() return None @cached_property def altitude_instrument(self): if self.instruments: from foundation.models.instrument import Instrument return self.instruments.filter( type_of=Instrument.INSTRUMENT_TYPE.ALTITUDE).first() return None @cached_property def water_level_instrument(self): if self.instruments: from foundation.models.instrument import Instrument return self.instruments.filter( type_of=Instrument.INSTRUMENT_TYPE.WATER_LEVEL).first() return None @cached_property def power_usage_instrument(self): if self.instruments: from foundation.models.instrument import Instrument return self.instruments.filter( type_of=Instrument.INSTRUMENT_TYPE.POWER_USAGE).first() return None @cached_property def ph_instrument(self): if self.instruments: from foundation.models.instrument import Instrument return self.instruments.filter( type_of=Instrument.INSTRUMENT_TYPE.PH).first() return None @cached_property def ec_instrument(self): if self.instruments: from foundation.models.instrument import Instrument return self.instruments.filter( type_of=Instrument.INSTRUMENT_TYPE.EC).first() return None @cached_property def orp_instrument(self): if self.instruments: from foundation.models.instrument import Instrument return self.instruments.filter( type_of=Instrument.INSTRUMENT_TYPE.ORP).first() return None @cached_property def camera_instrument(self): if self.instruments: from foundation.models.instrument import Instrument return self.instruments.filter( type_of=Instrument.INSTRUMENT_TYPE.CAMERA).first() return None @cached_property def heat_vision_instrument(self): if self.instruments: from foundation.models.instrument import Instrument return self.instruments.filter( type_of=Instrument.INSTRUMENT_TYPE.HEAT_VISION).first() return None @cached_property def uv_light_instrument(self): if self.instruments: from foundation.models.instrument import Instrument return self.instruments.filter( type_of=Instrument.INSTRUMENT_TYPE.UV_LIGHT).first() return None @cached_property def triad_spectroscopy_instrument(self): if self.instruments: from foundation.models.instrument import Instrument return self.instruments.filter( type_of=Instrument.INSTRUMENT_TYPE.TRIAD_SPECTROSCOPY).first() return None def set_last_recorded_datum(self, datum): # Update our value. self.last_measured_value = datum.value self.last_measured_at = datum.timestamp self.last_measured_unit_of_measure = datum.get_unit_of_measure() self.save() # Clear our cache of previously saved values. self.invalidate_all()
class Place(Base): # attribution attribution = TextField(null=True, blank=True, db_index=DB_INDEX) # concordances enwiki_title = TextField(null=True, blank=True, db_index=DB_INDEX) geonames_id = IntegerField(null=True, blank=True, db_index=DB_INDEX) osm_id = TextField(null=True, blank=True, db_index=DB_INDEX) pcode = TextField(null=True, blank=True, db_index=DB_INDEX) fips = IntegerField(null=True, blank=True, db_index=DB_INDEX) # admin stuff admin1_code = TextField(null=True, blank=True, db_index=DB_INDEX) admin2_code = TextField(null=True, blank=True, db_index=DB_INDEX) admin3_code = TextField(null=True, blank=True, db_index=DB_INDEX) admin4_code = TextField(null=True, blank=True, db_index=DB_INDEX) admin_level = IntegerField(null=True, blank=True, db_index=DB_INDEX) # bounding box stuff east = FloatField(null=True, blank=True) north = FloatField(null=True, blank=True) south = FloatField(null=True, blank=True) west = FloatField(null=True, blank=True) # name stuff name = TextField(null=True, blank=True, db_index=DB_INDEX) name_ascii = TextField(null=True, blank=True, db_index=DB_INDEX) name_display = TextField(null=True, blank=True, db_index=DB_INDEX) name_en = TextField(null=True, blank=True, db_index=DB_INDEX) name_normalized = TextField(null=True, blank=True, db_index=DB_INDEX) other_names = TextField(null=True, blank=True, db_index=False) # place types geonames_feature_class = TextField(null=True, blank=True, db_index=DB_INDEX) geonames_feature_code = TextField(null=True, blank=True, db_index=DB_INDEX) place_type = TextField(null=True, blank=True, db_index=DB_INDEX) # geometries objects = GeoManager() latitude = FloatField(null=True, blank=True) longitude = FloatField(null=True, blank=True) mls = MultiLineStringField(null=True, blank=True) mpoly = MultiPolygonField(null=True, blank=True) point = PointField(null=True, blank=True) area_sqkm = IntegerField(null=True, blank=True) # osm stuff importance = FloatField(null=True, blank=True) osmname_class = TextField(null=True, blank=True, db_index=DB_INDEX) osmname_type = TextField(null=True, blank=True, db_index=DB_INDEX) osm_type = TextField(null=True, blank=True, db_index=DB_INDEX) place_rank = IntegerField(null=True, blank=True, db_index=DB_INDEX) # dem and elevation stuff dem = FloatField(null=True, blank=True) elevation = FloatField(null=True, blank=True) # geocoder stuff city = TextField(null=True, blank=True, db_index=DB_INDEX) county = TextField(null=True, blank=True, db_index=DB_INDEX) # should be 100 country = TextField(null=True, blank=True, db_index=DB_INDEX) country_code = TextField(null=True, blank=True, db_index=DB_INDEX) state = TextField(null=True, blank=True, db_index=DB_INDEX) street = TextField(null=True, blank=True, db_index=DB_INDEX) #misc note = TextField(null=True, blank=True) population = BigIntegerField(null=True, blank=True, db_index=DB_INDEX) # number of times name appeared and meant this place minus number of times didn't mean this place popularity = BigIntegerField(null=True, blank=True, db_index=DB_INDEX) timezone = TextField(null=True, blank=True, db_index=DB_INDEX) topic = ForeignKey("Topic", null=True, on_delete=SET_NULL, db_index=DB_INDEX) # represents the most common topic associated with this place wikidata_id = TextField(null=True, blank=True, db_index=DB_INDEX) def get_all_names(self): if not hasattr(self, "all_names"): names = set() names.add(self.name) names.add(self.name_ascii) names.add(self.name_display) names.add(self.name_en) names.add(self.name_normalized) if self.other_names: for other_name in self.other_names.split(","): names.add(other_name) names.discard(None) self.all_names = names return self.all_names class Meta: ordering = ['name']
class Site(models.Model): identifier = models.CharField("ID", max_length=255) name = models.CharField(max_length=255) type = models.ForeignKey(ProjectType, verbose_name='Type of Site') phone = models.CharField(max_length=255, blank=True, null=True) address = models.TextField(blank=True, null=True) public_desc = models.TextField("Public Description", blank=True, null=True) additional_desc = models.TextField("Additional Description", blank=True, null=True) project = models.ForeignKey(Project, related_name='sites') logo = models.ImageField(upload_to="logo", default="logo/default_site_image.png") is_active = models.BooleanField(default=True) location = PointField(geography=True, srid=4326, blank=True, null=True) is_survey = models.BooleanField(default=False) date_created = models.DateTimeField(auto_now_add=True, blank=True) region = models.ForeignKey(Region, related_name='regions', blank=True, null=True) site_meta_attributes_ans = JSONField(default=list) logs = GenericRelation('eventlog.FieldSightLog') objects = GeoManager() class Meta: ordering = ['-is_active', '-id'] unique_together = [ ('identifier', 'project'), ] @property def latitude(self): if self.location: return self.location.y @property def longitude(self): if self.location: return self.location.x def getname(self): return self.name def __unicode__(self): return u'{}'.format(self.name) @property def get_supervisors(self): return self.site_roles.all() @property def get_supervisor_id(self): staffs = list(self.site_roles.filter(group__name="Site Supervisor")) if staffs: return [role.user.id for role in staffs] return [] def get_organization_name(self): return self.project.organization.name def get_project_name(self): return self.project.name def get_site_type(self): return self.type.name def progress(self): stages = self.site_forms.filter(xf__isnull=False, is_staged=True, is_deleted=False).count() approved = self.site_instances.filter( form_status=3, site_fxf__is_staged=True).count() if not approved: return 0 if not stages: return 0 p = ("%.0f" % (approved / (stages * 0.01))) p = int(p) if p > 99: return 100 return p @property def site_progress(self): return self.progress() @property def status(self): if self.site_instances.filter(form_status=1).count(): return 1 elif self.site_instances.filter(form_status=2).count(): return 2 elif self.site_instances.filter(form_status=0).count(): return 0 elif self.site_instances.filter(form_status=3).count(): return 3 return 4 def get_site_submission(self): instances = self.site_instances.all().order_by('-date') outstanding, flagged, approved, rejected = [], [], [], [] for submission in instances: if submission.form_status == 0: outstanding.append(submission) elif submission.form_status == 1: rejected.append(submission) elif submission.form_status == 2: flagged.append(submission) elif submission.form_status == 3: approved.append(submission) return outstanding, flagged, approved, rejected def get_site_submission_count(self): instances = self.site_instances.all().order_by('-date') outstanding, flagged, approved, rejected = 0, 0, 0, 0 for submission in instances: if submission.form_status == 0: outstanding += 1 elif submission.form_status == 1: rejected += 1 elif submission.form_status == 2: flagged += 1 elif submission.form_status == 3: approved += 1 response = {} response['outstanding'] = outstanding response['rejected'] = rejected response['flagged'] = flagged response['approved'] = approved return json.dumps(response) def get_absolute_url(self): return reverse('fieldsight:site-dashboard', kwargs={'pk': self.pk})
class Event(models.Model): name = models.CharField(max_length=200) start = models.DateTimeField() end = models.DateTimeField(blank=True, null=True) whole_day = models.BooleanField(default=False) timezone = models.CharField(max_length=100, blank=True, null=True) location_name = models.CharField(max_length=50, blank=True, null=True) location = PointField(blank=True, null=True) location_address = models.JSONField(blank=True, null=True) link = models.URLField(blank=True, null=True) kind = models.CharField(max_length=4, choices=[(x.name, x.value) for x in EventType]) description = models.TextField( blank=True, null=True, help_text= 'Tell people what the event is about and what they can expect. You may use Markdown in this field.' ) cancelled = models.BooleanField(default=False) def save(self, *args, **kwargs): if self.location: self.geocode_location() super().save(*args, **kwargs) def geocode_location(self): nr = requests.get('https://nominatim.openstreetmap.org/reverse', params={ 'format': 'jsonv2', 'lat': self.location.y, 'lon': self.location.x, 'accept-language': 'en' }) self.location_address = nr.json().get('address', None) if self.location_address is None: add_breadcrumb(category='nominatim', level='error', data=nr.json()) @property def location_text(self): if not self.location_address: return None addr = self.location_address return ", ".join( filter(lambda x: x is not None, [ addr.get('village'), addr.get('town'), addr.get('city'), addr.get('state'), addr.get('country') ])) @property def location_detailed_addr(self): # TODO: improve if not self.location_address: return None addr = self.location_address return ", ".join( filter(lambda x: x is not None, [ self.location_name, addr.get('house_number'), addr.get('road'), addr.get('suburb'), addr.get('village'), addr.get('city'), addr.get('state'), addr.get('country') ])) @property def start_localized(self): tz = timezone(self.timezone) return self.start.astimezone(tz) @property def end_localized(self): if not self.end: return None tz = timezone(self.timezone) return self.end.astimezone(tz) @property def tz_name(self): return get_timezone_name(self.start_localized) class Meta: indexes = (models.Index(fields=('end', )), )
class Accident(models.Model): id = models.CharField(max_length=13, primary_key=True) record_state = models.ForeignKey(AccidentRecordState, default=0, db_index=True) description = models.TextField(null=True, blank=True) location = PointField(db_index=True, null=True) police_force = models.ForeignKey(PoliceForce) severity = models.ForeignKey(CasualtySeverity) junction_control = models.ForeignKey(JunctionControl, null=True, blank=True) junction_detail = models.ForeignKey(JunctionDetail, null=True, blank=True) number_of_vehicles = models.SmallIntegerField() number_of_casualties = models.SmallIntegerField() casualty_distribution = models.ForeignKey(CasualtyDistribution, null=True, blank=True) vehicle_distribution = models.ForeignKey(VehicleDistribution, null=True, blank=True) date = models.DateField(db_index=True) date_and_time = models.DateTimeField(db_index=True, null=True, blank=True) police_attended = models.NullBooleanField() speed_limit = models.SmallIntegerField(null=True, blank=True) road_1_class = models.ForeignKey(RoadClass, related_name='accidents_1', null=True, blank=True) road_1_number = models.SmallIntegerField(null=True, blank=True) road_1 = models.CharField(max_length=10) road_2_class = models.ForeignKey(RoadClass, null=True, blank=True, related_name='accidents_2') road_2_number = models.SmallIntegerField(null=True, blank=True) road_2 = models.CharField(max_length=10, blank=True) pedestrian_crossing_human = models.ForeignKey(PedestrianCrossingHuman, null=True, blank=True) pedestrian_crossing_physical = models.ForeignKey( PedestrianCrossingPhysical, null=True, blank=True) light_conditions = models.ForeignKey(LightConditions, null=True, blank=True) weather = models.ForeignKey(Weather, null=True, blank=True) road_surface = models.ForeignKey(RoadSurface, null=True, blank=True) road_type = models.ForeignKey(RoadType, null=True, blank=True) special_conditions = models.ForeignKey(SpecialConditions, null=True, blank=True) carriageway_hazards = models.ForeignKey(CarriagewayHazards, null=True, blank=True) urban_rural = models.ForeignKey(UrbanRural, null=True, blank=True) highway_authority = models.ForeignKey(HighwayAuthority, db_index=True) solar_elevation = models.FloatField(null=True, blank=True) moon_phase = models.SmallIntegerField(null=True) has_citations = models.BooleanField(verbose_name='References?', default=False, db_index=True) @cached_property def annotation(self): from icw.annotation.models import AccidentAnnotation try: return self._annotation except AccidentAnnotation.DoesNotExist: return None def get_absolute_url(self): return reverse('accident-detail', args=(self.pk, )) def __str__(self): return self.id
class ParkingLocation(models.Model): spot = models.ForeignKey(Spot, related_name="parking_locations") location = PointField() description = models.CharField(max_length=500, null=True) created_at = models.DateTimeField(default=timezone.now) objects = GeoManager()
class Record(models.Model): location = PointField() title = models.CharField(max_length=30) created = models.DateTimeField(auto_now_add=True)
class Place(Model): CHOISES = [ ('О', 'обл. '), ('РГ', 'р-н '), ('РН', 'р-н '), ('М', 'м. '), ('Т', 'смт. '), ('С', 'с. '), ('Щ', 'с. '), ] id = IntegerField(primary_key=True, verbose_name="Код") parent_id = IntegerField(verbose_name="Родитель", db_index=True, null=True, blank=True,) category = CharField(max_length=2, choices=CHOISES, null=True, blank=True, verbose_name="Категория") name = CharField(max_length=255, verbose_name="Название") coordinates = PointField(verbose_name="Координаты", null=True, blank=True) rating = IntegerField(default=0, verbose_name='Рейтинг') is_location = BooleanField(default=False, verbose_name='Это НП?') is_active = BooleanField(default=True, verbose_name='Запись активна?') def __str__(self): name = self.name.capitalize() if name.endswith("район"): name = name.replace("район", "р-н") elif name.endswith("область"): name = name.replace("область", "обл.") elif ' ' in name: name = ' '.join([word.capitalize() for word in name.split()]) elif '-' in self.name: name = '-'.join([word.capitalize() for word in name.split('-')]) elif self.get_category_display(): name = self.get_category_display() + name return name def all_parents(self): all_parents = [] parent_id = self.parent_id while parent_id: parent = Place.objects.get(pk=parent_id) all_parents.append(parent) parent_id = parent.parent_id return all_parents def all_parents_name(self): return ' '.join(str(name) for name in self.all_parents()) def get_affiliations(self): region = 0 area = 0 parent_id = self.parent_id while parent_id: parent = Place.objects.get(pk=parent_id) if "РАЙОН" in parent.name: area = parent.id if parent.category == 'О': region = parent.id parent_id = parent.parent_id return {'region': region, 'area': area} def get_name_with_affiliations(self): affil = self.get_affiliations() try: area = str(Place.objects.get(pk=affil['area'])) except: area = "" try: region = str(Place.objects.get(pk=affil['region'])) except: region = "" full_name = { 'name': str(self), 'area': area, 'region': region, } return full_name def full_name(self): nwa = self.get_name_with_affiliations() return nwa['name'] + ' ' + nwa['area'] + ' ' + nwa['region'] def get_children(self): return Place.objects.filter(parent_id=self.id) def get_all_children(self):#TODO:set a maximum deep children_id=[] children = self.get_children() for child in children: if not child.get_children(): if child.is_location: children_id.append({'id': child.id, 'name': str(child)}) else: children_id.extend(child.get_all_children()) return children_id class Meta: verbose_name = 'Населенный пункт' verbose_name_plural = 'Населенные пункты'
class AttributeTypeGeoposition(AttributeTypeBase): attr_name = "Geoposition" dtype = "geopos" default = PointField(null=True, blank=True)
class Group(models.Model): name = models.CharField(max_length=64, null=True, blank=False, verbose_name="Group Name") slug = models.SlugField(null=True, blank=False, unique=True, max_length=100) signup_date = models.DateTimeField(null=True, blank=True, auto_now_add=True) group_id = models.CharField(max_length=4, null=True, blank=False, unique=True) # Order by group priority GROUP_TYPES = ((1, 'State Organizing Committee'), (2, 'State Chapter'), (3, 'Campus'), (4, 'Local Group')) group_type = models.IntegerField(blank=False, null=False, choices=GROUP_TYPES, default=4) # Individual Rep Email should match BSD authentication account rep_email = models.EmailField(null=True, blank=False, verbose_name="Contact Email", max_length=254) # Public group email does not need to match BSD authentication account group_contact_email = models.EmailField( blank=True, help_text="""Optional Group Contact Email to publicly display an email different from Group Leader Email""", max_length=254, null=True, ) rep_first_name = models.CharField(max_length=35, null=True, blank=False, verbose_name="First Name") rep_last_name = models.CharField(max_length=35, null=True, blank=False, verbose_name="Last Name") rep_postal_code = models.CharField(max_length=12, null=True, blank=True, verbose_name="Postal Code") rep_phone = PhoneNumberField(null=True, blank=True, verbose_name="Phone Number") county = models.CharField(max_length=64, null=True, blank=True) city = models.CharField(max_length=64, null=True, blank=True) state = USStateField(max_length=2, null=True, blank=True) postal_code = models.CharField(max_length=12, null=True, blank=True, verbose_name="Postal Code") country = CountryField(null=True, blank=False, default="US") point = PointField(null=True, blank=True) size = models.CharField(max_length=21, null=True, blank=True, verbose_name="Group Size") last_meeting = models.DateTimeField(null=True, blank=True, verbose_name="Date of Last Meeting") recurring_meeting = RecurrenceField(null=True, blank=True, verbose_name="Recurring Meeting") meeting_address_line1 = models.CharField("Address Line 1", max_length=45, null=True, blank=True) meeting_address_line2 = models.CharField("Address Line 2", max_length=45, null=True, blank=True) meeting_postal_code = models.CharField("Postal Code", max_length=12, null=True, blank=True) meeting_city = models.CharField(max_length=64, null=True, blank=True, verbose_name="City") meeting_state_province = models.CharField("State/Province", max_length=40, null=True, blank=True) meeting_country = CountryField(null=True, blank=True, verbose_name="Country", default='US') TYPES_OF_ORGANIZING_CHOICES = ( ('direct-action', 'Direct Action'), ('electoral', 'Electoral Organizing'), ('legistlative', 'Advocating for Legislation or Ballot Measures'), ('community', 'Community Organizing'), ('other', 'Other')) types_of_organizing = MultiSelectField(null=True, blank=True, choices=TYPES_OF_ORGANIZING_CHOICES, verbose_name="Types of Organizing") other_types_of_organizing = models.TextField( null=True, blank=True, verbose_name="Other Types of Organizing", max_length=500) description = models.TextField( null=True, blank=False, max_length=1000, verbose_name="Description (1000 characters or less)") issues = models.ManyToManyField(Issue, blank=True) other_issues = models.TextField(null=True, blank=True, max_length=250, verbose_name="Other Issues") constituency = models.TextField(null=True, blank=True, max_length=250) facebook_url = models.URLField(null=True, blank=True, verbose_name="Facebook URL", max_length=255) twitter_url = models.URLField(null=True, blank=True, verbose_name="Twitter URL", max_length=255) website_url = models.URLField(null=True, blank=True, verbose_name="Website URL", max_length=255) instagram_url = models.URLField(null=True, blank=True, verbose_name="Instagram URL", max_length=255) other_social = models.TextField(null=True, blank=True, verbose_name="Other Social Media", max_length=250) STATUSES = (('submitted', 'Submitted'), ('signed-mou', 'Signed MOU'), ('inactive', 'Inactive'), ('approved', 'Approved'), ('removed', 'Removed')) status = models.CharField(max_length=64, choices=STATUSES, default='submitted') VERSIONS = ( ('none', 'N/A'), ('1.0', 'Old'), ('1.1', 'Current'), ) signed_mou_version = models.CharField(max_length=64, choices=VERSIONS, default='none', verbose_name='MOU Version', null=True, blank=True) ORGANIZERS = ( ('juliana', 'Juliana'), ('basi', 'Basi'), ('kyle', 'Kyle'), ) organizer = models.CharField(max_length=64, choices=ORGANIZERS, default=None, verbose_name='Organizer', null=True, blank=True) mou_url = models.URLField(null=True, blank=True, verbose_name="MOU URL", max_length=255) """Admin Group Rating""" group_rating = models.IntegerField( blank=True, choices=group_rating_choices, null=True, ) # Notes field for internal OR staff use notes = models.TextField( blank=True, help_text="""Please include dates here along with notes to make reporting easier.""", null=True, verbose_name="Notes") def save(self, *args, **kwargs): # TODO: make main groups url an environment variable # and replace hardcoded /groups throughout site super(Group, self).save(*args, **kwargs) if self.slug: purge_url_from_cache('/groups/') purge_url_from_cache('/groups/' + self.slug + '/') def __unicode__(self): return self.name
class AbstractStore(models.Model): name = models.CharField(_('Name'), max_length=100) slug = models.SlugField(_('Slug'), max_length=100, null=True) # Contact details manager_name = models.CharField(_('Manager name'), max_length=200, blank=True, null=True) phone = models.CharField(_('Phone'), max_length=64, blank=True, null=True) email = models.CharField(_('Email'), max_length=100, blank=True, null=True) reference = models.CharField( _("Reference"), max_length=32, unique=True, null=True, blank=True, help_text=_("A reference number that uniquely identifies this store")) image = models.ImageField(_("Image"), upload_to="uploads/store-images", blank=True, null=True) description = models.CharField(_("Description"), max_length=2000, blank=True, null=True) location = PointField( _("Location"), srid=get_geodetic_srid(), ) group = models.ForeignKey('stores.StoreGroup', related_name='stores', verbose_name=_("Group"), null=True, blank=True) is_pickup_store = models.BooleanField(_("Is pickup store"), default=True) is_active = models.BooleanField(_("Is active"), default=True) objects = StoreManager() class Meta: abstract = True app_label = 'stores' ordering = ('name', ) def save(self, *args, **kwargs): if not self.slug: self.slug = slugify(self.name) super(AbstractStore, self).save(*args, **kwargs) def __str__(self): return self.name def get_absolute_url(self): return reverse('stores:detail', kwargs={ 'dummyslug': self.slug, 'pk': self.pk }) @property def has_contact_details(self): return any([self.manager_name, self.phone, self.email])
class Alert(models.Model): """ An alert incident. Attributes ---------- level : str The priority of the Alert. Options are constrained to :const:`~cyphon.choices.ALERT_LEVEL_CHOICES`. status : str The status of the Alert. Options are constrained to :const:`~cyphon.choices.ALERT_STATUS_CHOICES`. outcome : str The outcome of the Alert. Options are constrained to :const:`~cyphon.choices.ALERT_OUTCOME_CHOICES`. created_date : datetime The date and time the Alert was created. content_date : datetime The date and time the data that triggered the Alert was created. last_updated : datetime The date and time the Alert was last modified. assigned_user : AppUser The |AppUser| assigned to the Alert. alarm_type : ContentType The type of |Alarm| that triggered the Alert. alarm_id : int The |Alarm| object id of the |Alarm| that triggered the Alert. alarm : Alarm The |Alarm| object that generated the |Alert|, such as a |Watchdog| or |Monitor|. distillery : Distillery The |Distillery| associated with teh data that triggered the Alert. doc_id : str The id of the document that triggered the Alert. data : dict The document that triggered the Alert. location : `list` of `float` The longitude and latitude of the location associated with the Alert. title : str A title describing the nature of the Alert. incidents : int The number of duplicate incidents associated with the Alert. tag_relations : QuerySet of TagRelations TagRelations associated with the Alert. muzzle_hash : str If the Alert was generated by a |Watchdog| with an enabled |Muzzle|, represents a hash computed from characteristics of the |Muzzle| and the Alert. Otherwise, consists of a UUID. Used to identify duplicate Alerts. """ _WATCHDOG = models.Q(app_label='watchdogs', model='watchdog') _MONITOR = models.Q(app_label='monitors', model='monitor') _ALARMS = _WATCHDOG | _MONITOR _DEFAULT_TITLE = 'No title available' _HASH_FORMAT = ('{level}|{distillery}|{alarm_type}' '|{alarm_id}|{field_values}|{bucket:.0f}') level = models.CharField(max_length=20, choices=ALERT_LEVEL_CHOICES, db_index=True) status = models.CharField(max_length=20, choices=ALERT_STATUS_CHOICES, default='NEW', db_index=True) outcome = models.CharField(max_length=20, choices=ALERT_OUTCOME_CHOICES, null=True, blank=True, db_index=True) created_date = models.DateTimeField(default=timezone.now, db_index=True) content_date = models.DateTimeField(blank=True, null=True, db_index=True) last_updated = models.DateTimeField(auto_now=True, blank=True, null=True) assigned_user = models.ForeignKey(settings.AUTH_USER_MODEL, blank=True, null=True, on_delete=models.PROTECT) alarm_type = models.ForeignKey(ContentType, limit_choices_to=_ALARMS, blank=True, null=True, on_delete=models.PROTECT) alarm_id = models.PositiveIntegerField(blank=True, null=True) alarm = GenericForeignKey('alarm_type', 'alarm_id') distillery = models.ForeignKey(Distillery, blank=True, null=True, related_name='alerts', related_query_name='alerts', db_index=True, on_delete=models.PROTECT) doc_id = models.CharField(max_length=255, blank=True, null=True, db_index=True) data = JSONField(blank=True, null=True, default=dict) location = PointField(blank=True, null=True) title = models.CharField(max_length=255, blank=True, null=True) incidents = models.PositiveIntegerField(default=1) tag_relations = GenericRelation(TagRelation, related_query_name='alerts') muzzle_hash = models.CharField(max_length=64, blank=True, null=True, db_index=True, unique=True) objects = AlertManager() class Meta(object): """Metadata options.""" permissions = (('view_alert', 'Can see existing alerts'), ) ordering = ['-id'] def __str__(self): if self.title: return 'PK %s: %s' % (self.pk, self.title) elif self.pk: return 'PK %s' % self.pk else: return super(Alert, self).__str__() def save(self, *args, **kwargs): """ Overrides the save() method to assign a title, content_date, location, and data to a new Alert. """ if not self.data: self._add_data() if not self.location: self._add_location() if not self.content_date: self._add_content_date() if not self.title or self.title == self._DEFAULT_TITLE: self.title = self._format_title() # set the created_date now so it can be used to create the muzzle_hash if not self.created_date: self.created_date = timezone.now() self.muzzle_hash = self._get_muzzle_hash() return super(Alert, self).save(*args, **kwargs) @property def link(self): """ """ base_url = settings.BASE_URL return urllib.parse.urljoin(base_url, _ALERT_URL + str(self.id)) @property def coordinates(self): """ """ if self.location: return json.loads(self.location.json) @property def notes(self): """ """ if hasattr(self, 'analysis'): return self.analysis.notes def _add_content_date(self): """ Adds a content_date from the teaser's date if it exists. """ self.content_date = self.teaser.get('date') def _add_data(self): """ """ self.data = json_encodeable(self.saved_data) def _add_location(self): """ Adds a location if the Alert has one. """ self.location = self.teaser.get('location') def _get_muzzle(self): """ Get the Muzzle associated with an Alert, if one exists. """ if (self.alarm and hasattr(self.alarm, 'muzzle') and self.alarm.muzzle.enabled): return self.alarm.muzzle def _get_bucket(self, muzzle): """ Get the time bucket associated with an Alert and a given Muzzle. """ total_seconds = time.mktime(self.created_date.timetuple()) interval_seconds = convert_time_to_seconds(muzzle.time_interval, muzzle.time_unit) return total_seconds // interval_seconds def _get_field_values(self, muzzle): """ Get a string of field names and field values associated with the Muzzle of the Watchdog that created the Alert. """ field_values = [ ':'.join((field, get_dict_value(field, self.data) or '')) for field in sorted(muzzle.get_fields()) ] return ','.join(field_values) def _get_muzzle_hash(self): """ Return a muzzle_hash for the Alert. """ muzzle = self._get_muzzle() if muzzle: time_bucket = self._get_bucket(muzzle) field_values = self._get_field_values(muzzle) updated_hash = hashlib.sha256( self._HASH_FORMAT.format( level=self.level, distillery=self.distillery, alarm_type=self.alarm_type, alarm_id=self.alarm_id, field_values=field_values, bucket=time_bucket).encode()).hexdigest() # if the alert is being updated, only use the updated hash # if it doesn't match that of another alert if not (self.pk and updated_hash != self.muzzle_hash and Alert.objects.filter(muzzle_hash=updated_hash).exists()): return updated_hash return hashlib.sha256(uuid.uuid4().bytes).hexdigest() def _get_codebook(self): """ Returns the Codebook for the Distillery associated with the Alert. """ if self.distillery: return self.distillery.codebook def _format_title(self): """ If the Alert's teaser has title defined, returns the title. If not, returns an empty string. """ title = self.teaser.get('title', '') max_length = Alert._meta.get_field('title').max_length if title and len(title) > max_length: return title[:max_length] return title def _get_schema(self): """ Returns a list of DataFields in the Container associated with the Alert's data. """ if self.distillery: return self.distillery.schema def _summarize(self, include_empty=False): """ """ source_data = self.get_public_data_str() field_data = [ ('Alert ID', self.id), ('Title', self.title), ('Level', self.level), ('Incidents', self.incidents), ('Created date', self.created_date), ('\nCollection', self.distillery), ('Document ID', self.doc_id), ('Source Data', '\n' + source_data), ('\nNotes', '\n' + str(self.notes)), ] return format_fields(field_data, include_empty=include_empty) def _summarize_with_comments(self, include_empty=False): """ """ summary = self._summarize(include_empty=include_empty) separator = '\n\n' division = '-----' if self.comments.count() > 0: summary += separator summary += division for comment in self.comments.all(): summary += separator summary += comment.summary() return summary def display_title(self): """ Return the Alert's title or a default title. """ return self.title or self._DEFAULT_TITLE display_title.short_description = _('title') def redacted_title(self): """ Return a redacted version of the Alert's title or a default title. """ codebook = self._get_codebook() if self.title and codebook: return codebook.redact(self.title) else: return self.display_title() @cached_property def company(self): """ Returns the Company associated with the Alert's Distillery. """ if self.distillery: return self.distillery.company @property def saved_data(self): """ Attempts to locate the document which triggered the Alert. If successful, returns a data dictionary of the document. If not, returns an empty dictionary. """ has_setting = hasattr(settings, 'ALERTS') if has_setting and settings.ALERTS.get('DISABLE_COLLECTION_SEARCH'): return {} if self.distillery and self.doc_id: data = self.distillery.find_by_id(self.doc_id) if data: return data else: _LOGGER.warning('The document associated with id %s cannot be ' \ + 'found in %s.', self.doc_id, self.distillery) return {} @property def tidy_data(self): """ If the Alert's data is associated with a Container, returns a dictionary of the Alert's data containing only the fields in the Container. Otherwise, returns the Alert's data. """ schema = self._get_schema() if schema: return abridge_dict(schema, self.data) else: return self.data def get_data_str(self): """ Returns the Alert data as a pretty-print string. """ return json.dumps(self.data, sort_keys=True, indent=4) get_data_str.short_description = 'data' def get_public_data_str(self): """ Returns the Alert data as a pretty-print string, with private fields removed. """ public_data = {} for (key, val) in self.data.items(): if key not in _PRIVATE_FIELD_SETTINGS: public_data[key] = val return json.dumps(public_data, sort_keys=True, indent=4) @cached_property def teaser(self): """ Returns a Taste representing teaser data for the document which generated the Alert. """ if self.distillery: return self.distillery.get_sample(self.data) else: return {} @property def associated_tags(self): """ Returns a QuerySet of Tags associated with the Alert or its comments. """ comment_ids = self.comments.all().values_list('id', flat=True) alert_relations = models.Q( content_type=ContentType.objects.get_for_model(Alert), object_id=self.id) analysis_relations = models.Q( content_type=ContentType.objects.get_for_model(Analysis), object_id=self.id) comment_relations = models.Q( content_type=ContentType.objects.get_for_model(Comment), object_id__in=comment_ids) query = alert_relations | analysis_relations | comment_relations tag_relations = TagRelation.objects.filter(query) return Tag.objects.filter(tag_relations__in=tag_relations).distinct() def add_incident(self): """ Increments the number of incidents associated with the Alert. """ # using F instead of += increments the value using a SQL query # and avoids race conditions self.incidents = models.F('incidents') + 1 self.save() def summary(self, include_empty=False, include_comments=False): """ """ if include_comments: return self._summarize_with_comments(include_empty=include_empty) else: return self._summarize(include_empty=include_empty)
class Shelter(models.Model): def _shelter_square_logo_file(self, filename: str) -> str: ext = file_extension(filename) slug = slugify(self.name) filename = f"{slug}-square-logo.{ext}" return join('img', 'web', 'shelter', slug, filename) name = models.CharField(max_length=50, verbose_name=_("Prieglaudos pavadinimas")) slug = models.SlugField(unique=True, editable=False) order = models.IntegerField(default=0, editable=False) legal_name = models.CharField(max_length=256, null=True, verbose_name=_("Įstaigos pavadinimas")) is_published = models.BooleanField( default=False, db_index=True, verbose_name=_("Paskelbta"), help_text=_("Pažymėjus prieglauda matoma viešai")) square_logo = models.ImageField(upload_to=_shelter_square_logo_file, verbose_name=_("Kvadratinis logotipas")) region = models.ForeignKey(Region, on_delete=models.PROTECT, related_name="shelters", verbose_name=_("Regionas")) address = models.CharField(max_length=256, verbose_name=_("Prieglaudos adresas")) location = PointField(verbose_name=_("Vieta")) email = models.EmailField(verbose_name=_("Elektroninis paštas")) phone = models.CharField(max_length=24, verbose_name=_("Telefono numeris")) website = models.URLField(blank=True, null=True, verbose_name=_("Interneto svetainė")) facebook = models.URLField(blank=True, null=True, verbose_name=_("Facebook")) instagram = models.URLField(blank=True, null=True, verbose_name=_("Instagram")) authenticated_users = models.ManyToManyField( settings.AUTH_USER_MODEL, blank=True, limit_choices_to=models.Q(groups__name=_SHELTER_GROUP_NAME, is_staff=True, _connector=models.Q.OR), verbose_name=_("Vartotojai tvarkantys prieglaudos informaciją"), help_text= _("Priskirti vartotojai gali matyti prieglaudos gyvūnus ir juos tvarkyti." )) created_at = models.DateTimeField(auto_now_add=True, verbose_name=_('Sukūrimo data')) updated_at = models.DateTimeField(auto_now=True, verbose_name=_("Atnaujinimo data")) objects = ShelterQuerySet.as_manager() available = SheltersManager() class Meta: verbose_name = _("Gyvūnų prieglauda") verbose_name_plural = _("Gyvūnų prieglaudos") default_related_name = "shelters" ordering = ("order", "id") index_together = [ ("order", "id"), ] def save(self, force_insert=False, force_update=False, using=None, update_fields=None): self.slug = slugify(self.name) super().save(force_insert, force_update, using, update_fields) @staticmethod def user_associated_shelters(user: AbstractBaseUser) -> QuerySet[Shelter]: if user.is_authenticated: return Shelter.objects.filter(authenticated_users=user) return Shelter.objects.none() @staticmethod def user_associated_shelter_by_id(user: AbstractBaseUser, shelter_id: int) -> Optional[Shelter]: return Shelter.user_associated_shelters(user).filter( id=shelter_id).first() @staticmethod def user_associated_shelter(request: HttpRequest) -> Optional[Shelter]: shelters = Shelter.user_associated_shelters(request.user) if cookie_shelter_id := try_parse_int( request.COOKIES.get(Constants.SELECTED_SHELTER_COOKIE_ID, None)): shelter_from_cookie = shelters.filter(id=cookie_shelter_id).first() if shelter_from_cookie: return shelter_from_cookie return shelters.first()
newaction = Action( actor_content_type=ContentType.objects.get_for_model(actor), actor_object_id=actor.pk, verb=unicode(verb), place=kwargs.pop('place', None), # TODO get automatically from target obj? public=bool(kwargs.pop('public', True)), description=kwargs.pop('description', None), timestamp=kwargs.pop('timestamp', now())) for opt in ('target', 'action_object'): obj = kwargs.pop(opt, None) if not obj is None: check_actionable_model(obj) setattr(newaction, '%s_object_id' % opt, obj.pk) setattr(newaction, '%s_content_type' % opt, ContentType.objects.get_for_model(obj)) if actstream_settings.USE_JSONFIELD and len(kwargs): newaction.data = kwargs newaction.save() # use our action handler action.disconnect(dispatch_uid='actstream.models') action.connect(place_action_handler, dispatch_uid='activity_stream.models') # make Action.place available PointField(blank=True, null=True).contribute_to_class(Action, 'place')
class Project(models.Model): name = models.CharField(max_length=255) type = models.ForeignKey(ProjectType, verbose_name='Type of Project') phone = models.CharField(max_length=255, blank=True, null=True) fax = models.CharField(max_length=255, blank=True, null=True) email = models.EmailField(blank=True, null=True) address = models.TextField(blank=True, null=True) website = models.URLField(blank=True, null=True) donor = models.CharField(max_length=256, blank=True, null=True) public_desc = models.TextField("Public Description", blank=True, null=True) additional_desc = models.TextField("Additional Description", blank=True, null=True) organization = models.ForeignKey(Organization, related_name='projects') logo = models.ImageField(upload_to="logo", default="logo/default_project_image.jpg") is_active = models.BooleanField(default=True) location = PointField(geography=True, srid=4326, blank=True, null=True) date_created = models.DateTimeField(auto_now_add=True, blank=True) cluster_sites = models.BooleanField(default=False) site_meta_attributes = JSONField(default=list) logs = GenericRelation('eventlog.FieldSightLog') objects = GeoManager() class Meta: ordering = [ '-is_active', 'name', ] @property def latitude(self): if self.location: return self.location.y @property def longitude(self): if self.location: return self.location.x def getname(self): return self.name def __unicode__(self): return u'{}'.format(self.name) @property def get_staffs(self): staffs = self.project_roles.filter( group__name__in=["Reviewer", "Project Manager"]) return staffs @property def get_staffs_both_role(self): managers_id = self.project_roles.filter( group__name="Project Manager").values_list('user__id', flat=True) reviewers_id = self.project_roles.filter( group__name="Reviewer").values_list('user__id', flat=True) both = list(set(managers_id).intersection(reviewers_id)) return both def get_organization_name(self): return self.organization.name def get_project_type(self): return self.type.name @property def status(self): if self.project_instances.filter(form_status=1).count(): return 1 elif self.project_instances.filter(form_status=2).count(): return 2 elif self.project_instances.filter(form_status=0).count(): return 0 elif self.project_instances.filter(form_status=3).count(): return 3 return 4 def get_project_submission(self): instances = self.project_instances.all().order_by('-date') outstanding, flagged, approved, rejected = [], [], [], [] for submission in instances: if submission.form_status == 0: outstanding.append(submission) elif submission.form_status == 1: rejected.append(submission) elif submission.form_status == 2: flagged.append(submission) elif submission.form_status == 3: approved.append(submission) return outstanding, flagged, approved, rejected def get_submissions_count(self): outstanding = self.project_instances.filter(form_status=0).count() rejected = self.project_instances.filter(form_status=1).count() flagged = self.project_instances.filter(form_status=2).count() approved = self.project_instances.filter(form_status=3).count() return outstanding, flagged, approved, rejected def get_absolute_url(self): return reverse('fieldsight:project-dashboard', kwargs={'pk': self.pk})
class Building(models.Model): address = models.CharField(max_length=75, unique=True) coordinates = PointField(geography=True, unique=True) class Meta: ordering = ('address', )
class Organization(models.Model): name = models.CharField("Organization Name", max_length=255) type = models.ForeignKey(OrganizationType, verbose_name='Type of Organization') phone = models.CharField("Contact Number", max_length=255, blank=True, null=True) fax = models.CharField(max_length=255, blank=True, null=True) email = models.EmailField(blank=True, null=True) website = models.URLField(blank=True, null=True) country = models.CharField(max_length=3, choices=COUNTRIES, default=u'NPL') address = models.TextField(blank=True, null=True) public_desc = models.TextField("Public Description", blank=True, null=True) additional_desc = models.TextField("Additional Description", blank=True, null=True) logo = models.ImageField(upload_to="logo", default="logo/default_org_image.jpg") is_active = models.BooleanField(default=True) location = PointField( geography=True, srid=4326, blank=True, null=True, ) date_created = models.DateTimeField(auto_now_add=True, blank=True) logs = GenericRelation('eventlog.FieldSightLog') class Meta: ordering = [ '-is_active', 'name', ] def __unicode__(self): return u'{}'.format(self.name) objects = GeoManager() @property def latitude(self): if self.location: return self.location.y @property def longitude(self): if self.location: return self.location.x def getname(self): return self.name @property def status(self): if self.organization_instances.filter(form_status=1).count(): return 1 elif self.organization_instances.filter(form_status=2).count(): return 2 elif self.organization_instances.filter(form_status=0).count(): return 0 elif self.organization_instances.filter(form_status=3).count(): return 3 return 4 def get_organization_submission(self): instances = self.organization_instances.all().order_by('-date') outstanding, flagged, approved, rejected = [], [], [], [] for submission in instances: if submission.form_status == 0: outstanding.append(submission) elif submission.form_status == 1: rejected.append(submission) elif submission.form_status == 2: flagged.append(submission) elif submission.form_status == 3: approved.append(submission) return outstanding, flagged, approved, rejected def get_submissions_count(self): from onadata.apps.fsforms.models import FInstance outstanding = FInstance.objects.filter(project__organization=self, form_status=0).count() rejected = FInstance.objects.filter(project__organization=self, form_status=1).count() flagged = FInstance.objects.filter(project__organization=self, form_status=2).count() approved = FInstance.objects.filter(project__organization=self, form_status=3).count() return outstanding, flagged, approved, rejected def get_absolute_url(self): return reverse('fieldsight:organizations-dashboard', kwargs={'pk': self.pk}) @property def get_staffs(self): staffs = self.organization_roles.filter( group__name="Organization Admin").values_list( 'id', 'user__username') return staffs @property def get_staffs_org(self): staffs = self.organization_roles.filter( group__name="Organization Admin") return staffs @property def get_staffs_id(self): return self.organization_roles.filter( group__name="Organization Admin").values_list('id', flat=True) def get_organization_type(self): return self.type.name
class User(Model): location = PointField(null=True, spatial_index=True) class Meta: AbstractUser
class Location(models.Model): point = PointField(srid=DEFAULT_SRID, null=True, blank=True)
class Place(Model): city = ForeignKey('City') name = TextField() point = PointField()
class PostalCode(Place, SlugModel): slug_contains_id = True code = models.CharField(max_length=20) location = PointField() country = models.ForeignKey(swapper.get_model_name('cities', 'Country'), related_name='postal_codes', on_delete=SET_NULL_OR_CASCADE) # Region names for each admin level, region may not exist in DB region_name = models.CharField(max_length=100, db_index=True) subregion_name = models.CharField(max_length=100, db_index=True) district_name = models.CharField(max_length=100, db_index=True) region = models.ForeignKey(Region, blank=True, null=True, related_name='postal_codes', on_delete=SET_NULL_OR_CASCADE) subregion = models.ForeignKey(Subregion, blank=True, null=True, related_name='postal_codes', on_delete=SET_NULL_OR_CASCADE) city = models.ForeignKey(swapper.get_model_name('cities', 'City'), blank=True, null=True, related_name='postal_codes', on_delete=SET_NULL_OR_CASCADE) district = models.ForeignKey(District, blank=True, null=True, related_name='postal_codes', on_delete=SET_NULL_OR_CASCADE) objects = GeoManager() class Meta: unique_together = ( ('country', 'region', 'subregion', 'city', 'district', 'name', 'id', 'code'), ('country', 'region_name', 'subregion_name', 'district_name', 'name', 'id', 'code'), ) @property def parent(self): return self.country @property def name_full(self): """Get full name including hierarchy""" return force_text(', '.join(reversed(self.names))) @property def names(self): """Get a hierarchy of non-null names, root first""" return [ e for e in [ force_text(self.country), force_text(self.region_name), force_text(self.subregion_name), force_text(self.district_name), force_text(self.name), ] if e ] def __str__(self): return force_text(self.code) def slugify(self): if self.id: return '{}-{}'.format(self.id, unicode_func(self.code)) return None
class Update(models.Model): class Meta: abstract = True ordering = ["-timestamp"] timestamp = models.DateTimeField(default=timezone.now) point = PointField(blank=True, null=True) closest_mile = models.ForeignKey( HalfmileWaypoint, blank=True, null=True, on_delete=models.SET_NULL, related_name="+", limit_choices_to={"type": HalfmileWaypoint.MILE_TYPE}, ) closest_poi = models.ForeignKey( HalfmileWaypoint, blank=True, null=True, on_delete=models.SET_NULL, related_name="+", limit_choices_to={"type": HalfmileWaypoint.POI_TYPE}, ) location_override = models.TextField(blank=True, default="") show_on_timeline = models.BooleanField(default=True) deleted = models.BooleanField(default=False) @classmethod def recent_updates(klass, n=50): type_qs_map = {} for model in Update.__subclasses__(): model_name = model.__name__.lower() qs = model.objects.filter(show_on_timeline=True, deleted=False) qs = qs.select_related("closest_mile", "closest_poi") type_qs_map[model_name] = qs return combined_recent(100, datetime_field="timestamp", **type_qs_map) def save(self, *args, **kwargs): # Attempt to fill in missing location info based on what's there. # If there's a point but no waypoints, try to fill in waypoints based on proximity. # If there's a waypoint but no point, copy the waypoint's location to this. # The order's sneaky: do the waypoint -> point bit first, because that way a # waypoint of one type (POI or mile) will make there be a point, which'll then # fill in the waypoint of the other type. if not self.point: if self.closest_mile: self.point = self.closest_mile.point elif self.closest_poi: self.point = self.closest_poi.point if self.point and not self.closest_mile: self.closest_mile = HalfmileWaypoint.objects.closest_to( self.point, type=HalfmileWaypoint.MILE_TYPE) if self.point and not self.closest_poi: self.closest_poi = HalfmileWaypoint.objects.closest_to( self.point, type=HalfmileWaypoint.POI_TYPE) super().save(*args, **kwargs) def get_absolute_url(self): return reverse("index") + f"#{self._meta.model_name}-{self.pk}" @property def latitude(self): return self.point.y @property def longitude(self): return self.point.x @property def location_name(self): if self.location_override: return self.location_override elif self.closest_poi: return camel_to_spaced(self.closest_poi.name) elif self.closest_mile: return str(self.closest_mile) elif self.point: return str(self.point) else: return f"unknown location" def __str__(self): return f"{self.__class__.__name__} at {self.location_name}"
def __init__(self, based_fields=None, zoom=None, suffix='', *args, **kwargs): super(LocationField, self).__init__(based_fields=based_fields, zoom=zoom, suffix=suffix, *args, **kwargs) PointField.__init__(self, *args, **kwargs)
class Provider(models.Model): organization = models.ForeignKey( Organization, related_name='providers', on_delete=models.SET_NULL, null=True, ) store_number = models.PositiveIntegerField( _('store number'), default=0, ) name = models.CharField( _('provider name'), max_length=255, ) type = models.ForeignKey( ProviderType, related_name='providers', on_delete=models.SET_NULL, null=True, ) category = models.ForeignKey( ProviderCategory, related_name='providers', on_delete=models.SET_NULL, null=True, ) address = models.CharField( _('provider address'), max_length=255, ) city = models.CharField( _('provider city'), max_length=255, ) state = USStateField( _('us state'), validators=[validate_state], ) related_state = models.ForeignKey( State, related_name='providers', on_delete=models.SET_NULL, null=True, ) related_county = models.ForeignKey( County, related_name='providers', on_delete=models.SET_NULL, null=True, ) zip = USZipCodeField( _('zip code'), validators=[validate_zip], ) related_zipcode = models.ForeignKey( ZipCode, related_name='providers', on_delete=models.SET_NULL, null=True, ) relate_related_zipcode = models.BooleanField( _('relate zipcode'), default=False, help_text=_( 'Check if you need the system to relate a new zipcode object' ' to this provider. Generally you should use this only if you' ' are an admin changing the direction of this provider'), ) phone = PhoneNumberField(_('provider phone'), ) website = models.URLField( _('provider website'), max_length=255, blank=True, ) email = models.EmailField( _('provider email address'), unique=True, error_messages={ 'unique': 'A provider with that email already exists.', }, null=True, ) operating_hours = models.CharField( _('operating hours'), max_length=255, blank=True, ) notes = models.TextField( _('notes'), blank=True, ) insurance_accepted = models.BooleanField( _('insurance accepted'), default=False, ) lat = models.CharField( _('latitude'), blank=True, null=True, max_length=250, ) lng = models.CharField( _('longitude'), blank=True, null=True, max_length=250, ) geo_localization = PointField( _('localization'), null=True, ) change_coordinates = models.BooleanField( _('change coordinates'), default=False, help_text=_('Check this if you want the application to recalculate the' ' coordinates. Used in case address has been changed'), ) start_date = models.DateTimeField( _('start date'), null=True, blank=True, ) end_date = models.DateTimeField( _('start date'), null=True, blank=True, ) walkins_accepted = models.NullBooleanField(_('walkins accepted'), ) last_import_date = models.DateTimeField( _('last import date'), auto_now_add=True, help_text=_('Last time this provider uploaded new information.'), ) active = models.BooleanField( _('active'), default=True, ) home_delivery = models.BooleanField( _('home delivery'), default=False, ) home_delivery_info_url = models.URLField( _('home delivery info url'), max_length=255, blank=True, ) vaccine_finder_id = models.PositiveIntegerField( _('vaccine finder id'), null=True, ) vaccine_finder_type = models.PositiveIntegerField( _('vaccine finder type'), null=True, ) objects = ActiveProviderManager() class Meta: verbose_name = _('provider') verbose_name_plural = _('providers') indexes = [ models.Index(fields=[ 'address', 'city', 'organization_id', 'phone', 'related_zipcode_id', 'state', 'store_number', 'zip' ]) ] def __str__(self): return '{} - store number: {}'.format( self.name if self.name else 'provider', self.store_number, ) # Geocode using full address def _get_full_address(self): return '{} {} {} {} {}'.format( self.address, self.city, self.state, COUNTRY, self.zip, ) full_address = property(_get_full_address) def save(self, *args, **kwargs): if self.change_coordinates or not self.pk: location = '+'.join( filter(None, ( self.address, self.city, self.state, COUNTRY, ))) self.lat, self.lng = get_lat_lng(location) if self.lat and self.lng: self.geo_localization = Point( float(self.lng), float(self.lat), ) self.change_coordinates = False if self.relate_related_zipcode and self.zip: zipcode = False try: zipcode = ZipCode.objects.get( zipcode=self.zip[:5], state__state_code=self.state, ) except ZipCode.DoesNotExist: pass except MultipleObjectsReturned: zipcode = ZipCode.objects.filter( zipcode=self.zip, state__state_code=self.state, ).first() if zipcode: self.related_zipcode = zipcode self.relate_related_zipcode = False if self.related_zipcode and not self.related_state_id: self.related_state_id = self.related_zipcode.state_id if self.related_zipcode and not self.related_county: county_ids = [] for county in self.related_zipcode.counties.all(): county_ids.append(county.id) if len(county_ids) > 0: self.related_county_id = county_ids[0] super().save(*args, **kwargs)
class BusinessLocation(MetaDataAbstract): class Meta: unique_together = (("state_fk", "city_fk", "slug_name"),) CATEGORY_CHOICES = ( ('dispensary', 'Dispensary'), ('delivery', 'Delivery'), ('grow_house', 'Cultivator'), ) DEFAULT_IMAGE_URL = '{base}images/default-location-image.jpeg'.format(base=settings.STATIC_URL) STRAIN_INDEX_FIELDS = ['dispensary', 'delivery', 'grow_house', 'delivery_radius', 'lat', 'lng', 'removed_date'] business = models.ForeignKey(Business, on_delete=models.CASCADE) location_name = models.CharField(max_length=255, blank=False, null=False) manager_name = models.CharField(max_length=255, blank=True, null=True) location_email = models.CharField(max_length=255, blank=True, null=True) image = models.ImageField(max_length=255, upload_to=upload_business_location_image_to, blank=True, help_text='Maximum file size allowed is 5Mb', validators=[validate_business_image]) category = models.CharField(max_length=20, default='dispensary', choices=CATEGORY_CHOICES) slug_name = models.SlugField(max_length=611, null=True, blank=True, help_text='This will be automatically generated from a location name when created') primary = models.BooleanField(default=False) dispensary = models.BooleanField(default=False) delivery = models.BooleanField(default=False) grow_house = models.BooleanField(default=False) delivery_radius = models.FloatField(max_length=10, blank=True, null=True) grow_details = JSONField(default={'organic': False, 'pesticide_free': False, 'indoor': False, 'outdoor': False}, blank=True, null=True) street1 = models.CharField(max_length=100, blank=True) city = models.CharField(max_length=100) state = models.CharField(max_length=50) zip_code = models.CharField(max_length=10, db_index=True, blank=True) timezone = models.CharField(max_length=100, null=True, choices=zip(pytz.common_timezones, pytz.common_timezones)) city_slug = models.SlugField(max_length=611, null=True, blank=True, help_text='This will be automatically generated from a city when updated') state_fk = models.ForeignKey(State, on_delete=models.DO_NOTHING, null=True, related_name='business_locations') city_fk = models.ForeignKey(City, on_delete=models.DO_NOTHING, null=True, related_name='business_locations') about = models.TextField(blank=True, null=True, default='') lat = models.FloatField(_('Latitude'), blank=True, null=True, max_length=50) lng = models.FloatField(_('Longitude'), blank=True, null=True, max_length=50) location_raw = JSONField(_('Location Raw JSON'), default={}, blank=True, null=True, max_length=20000) geo_location = PointField(geography=True, srid=4326, null=True, db_index=True) phone = models.CharField(max_length=15, blank=True, null=True, validators=[phone_number_validator]) ext = models.CharField(max_length=5, blank=True, null=True) verified = models.BooleanField(default=False) removed_by = models.CharField(max_length=20, blank=True, null=True) removed_date = models.DateTimeField(blank=True, null=True) created_date = models.DateTimeField(auto_now_add=True) menu_updated_date = models.DateField(null=True) mon_open = models.TimeField(blank=True, null=True) mon_close = models.TimeField(blank=True, null=True) tue_open = models.TimeField(blank=True, null=True) tue_close = models.TimeField(blank=True, null=True) wed_open = models.TimeField(blank=True, null=True) wed_close = models.TimeField(blank=True, null=True) thu_open = models.TimeField(blank=True, null=True) thu_close = models.TimeField(blank=True, null=True) fri_open = models.TimeField(blank=True, null=True) fri_close = models.TimeField(blank=True, null=True) sat_open = models.TimeField(blank=True, null=True) sat_close = models.TimeField(blank=True, null=True) sun_open = models.TimeField(blank=True, null=True) sun_close = models.TimeField(blank=True, null=True) MetaDataAbstract._meta.get_field('meta_title').help_text = _( 'Leave the field blank to display the default title as ' '`{ location_name } Dispensary in { city }, { street1 }` for dispensary page and ' '`{ location_name } Cultivator in { city }, { street1 }` for cultivator page.') MetaDataAbstract._meta.get_field('meta_desc').help_text = _( 'Leave the field blank to display the default description as ' '`StrainRx brings you the most up to date menu and the latest deals from { location_name } in { city }`') objects = GeoManager() def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.original_location = {field: getattr(self, field) for field in self.STRAIN_INDEX_FIELDS} self.original_location_name = self.location_name @property def url(self): if self.dispensary or self.delivery: return reverse('businesses:dispensary_info', kwargs={'state': self.state_fk.abbreviation.lower(), 'city_slug': self.city_fk.full_name_slug, 'slug_name': self.slug_name}) return reverse('businesses:grower_info', kwargs={'state': self.state_fk.abbreviation.lower(), 'city_slug': self.city_fk.full_name_slug, 'slug_name': self.slug_name}) @property def urls(self): urls = {} kwargs = { 'state': self.state_fk.abbreviation.lower(), 'city_slug': self.city_fk.full_name_slug, 'slug_name': self.slug_name, } if self.dispensary or self.delivery: urls['dispensary'] = reverse('businesses:dispensary_info', kwargs=kwargs) if self.grow_house: urls['grow_house'] = reverse('businesses:grower_info', kwargs=kwargs) return urls @property def image_url(self): # helper to get image url or return default if self.image and hasattr(self.image, 'url') and self.image.url: return self.image.url else: return self.DEFAULT_IMAGE_URL @property def about_or_default(self): if self.about: return self.about types = [] if self.dispensary: types.append('dispensary') if self.grow_house: types.append('cultivator') if self.delivery: types.append('delivery') if len(types) == 0: combined_type = 'business' elif len(types) == 1: combined_type = types[0] elif len(types) == 2: combined_type = ' and '.join(types) else: combined_type = ', '.join(types[:-1]) + ' and ' + types[-1] template = '{name} is a Marijuana {combined_type} located in {city}, {state}' context = { 'name': self.location_name, 'combined_type': combined_type, 'city': self.city, 'state': self.state, } return template.format(**context) @property def formatted_address(self): return ', '.join(filter(None, (self.street1, self.city, self.zip_code, self.state))) @property def days_since_menu_update(self): if not self.menu_updated_date: return -1 return (self.get_current_datetime().date() - self.menu_updated_date).days def get_absolute_url(self): return self.url def can_user_request_menu_update(self, user): if not user.is_authenticated(): return False, 'User has to be logged in' if 0 <= self.days_since_menu_update <= 3: return False, 'Menu has been updated recently' recent_requests = BusinessLocationMenuUpdateRequest.objects.filter( business_location=self, user=user, date_time__gt=datetime.now(pytz.utc) - timedelta(days=1), ) if recent_requests.exists(): return False, 'You have recently requested a menu update' return True, None def get_current_datetime(self): if not self.timezone: timezone = 'UTC' else: timezone = self.timezone return datetime.now(pytz.timezone(timezone)) def is_searchable(self): if self.removed_by or self.removed_date: return False return self.business.is_searchable def save(self, *args, **kwargs): if (self.pk is None and not self.slug_name) or (self.pk and self.original_location_name != self.location_name): # determine a category self.category = 'dispensary' if self.dispensary else 'delivery' if self.delivery else 'dispensary' # create a slug name slugified_name = slugify(self.location_name) slugified_name_and_street = '{0}-{1}'.format(slugify(self.location_name), slugify(self.street1)) if not exist_by_slug_name(slugified_name): self.slug_name = slugified_name elif not exist_by_slug_name(slugified_name_and_street): self.slug_name = slugified_name_and_street else: for x in range(1, 1000): new_slug_name = '{0}-{1}'.format(slugified_name_and_street, x) if not exist_by_slug_name(new_slug_name): self.slug_name = new_slug_name break self.original_location_name = self.location_name if self.city: self.city_slug = slugify(self.city) self.geo_location = Point(self.lng, self.lat) super(BusinessLocation, self).save(*args, **kwargs) def clean(self): if not any((self.delivery, self.dispensary, self.grow_house)): raise ValidationError('Business Location needs to be one of the ' 'following: delivery, dispensary or cultivator.') def __str__(self): return self.location_name
class OnboardingInfo(models.Model): """ The details a user filled out when they joined the site. Note that this model was originally NYC-specific, but was subsequently changed to support non-NYC users. As such, it has a few wrinkles. """ def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) # This keeps track of the fields that comprise our NYC address. self.__nycaddr = InstanceChangeTracker(self, ["address", "borough"]) # This keeps track of fields that comprise metadata about our NYC address, # which can be determined from the fields comprising our address. self.__nycaddr_meta = InstanceChangeTracker( self, ["geocoded_address", "zipcode", "geometry", "pad_bbl", "pad_bin"]) # This keeps track of the fields that comprise our non-NYC address. self.__nationaladdr = InstanceChangeTracker( self, ["address", "non_nyc_city", "state", "zipcode"]) # This keeps track of fields that comprise metadata about our non-NYC address, # which can be determined from the fields comprising our address. self.__nationaladdr_meta = InstanceChangeTracker( self, ["geocoded_address", "geometry"]) created_at = models.DateTimeField(auto_now_add=True) updated_at = models.DateTimeField(auto_now=True) user = models.OneToOneField(JustfixUser, on_delete=models.CASCADE, related_name="onboarding_info") signup_intent = models.CharField( max_length=30, choices=SIGNUP_INTENT_CHOICES.choices, help_text="The reason the user originally signed up with us.", ) address = models.CharField( **ADDRESS_FIELD_KWARGS, help_text= "The user's address. Only street name and number are required.", ) # TODO: This is currently only used for NYC-based users, and we might want to # deprecate it entirely: https://github.com/JustFixNYC/tenants2/issues/1991 address_verified = models.BooleanField( default=False, help_text=( "Whether we've verified, on the server-side, that the user's " "address is valid."), ) geocoded_address = models.CharField( max_length=255, blank=True, help_text= ("This is the user's definitive street address returned by the geocoder, and " "what the user's latitude, longitude, and other attributes are based from. This " "should not be very different from the address field (if it is, you " "may need to change the address so the geocoder matches to the " "proper location)."), ) borough = models.CharField( **BOROUGH_FIELD_KWARGS, blank=True, help_text=( "The New York City borough the user's address is in, if they " "live inside NYC."), ) non_nyc_city = models.CharField( **CITY_KWARGS, blank=True, help_text=( "The non-NYC city the user's address is in, if they live outside " "of NYC."), ) state = models.CharField( **STATE_KWARGS, help_text='The two-letter state or territory of the user, e.g. "NY".') zipcode = models.CharField( # https://stackoverflow.com/q/325041/2422398 max_length=12, blank=True, validators=[ZipCodeValidator()], help_text=f"The user's ZIP code. {NYCADDR_META_HELP}", ) geometry = models.JSONField( blank=True, null=True, help_text= "The GeoJSON point representing the user's address, if available.", ) geocoded_point = PointField( null=True, blank=True, srid=4326, help_text="The point representing the user's address, if available.", ) pad_bbl: str = models.CharField( max_length=PAD_BBL_DIGITS, blank=True, help_text= f"The user's Boro, Block, and Lot number. {NYCADDR_META_HELP}", ) pad_bin: str = models.CharField( max_length=PAD_BIN_DIGITS, blank=True, help_text= f"The user's building identification number (BIN). {NYCADDR_META_HELP}", ) apt_number = models.CharField(**APT_NUMBER_KWARGS, blank=True) floor_number = models.PositiveSmallIntegerField( null=True, blank=True, help_text="The floor number the user's apartment is on.") is_in_eviction = models.BooleanField( null=True, blank=True, help_text="Has the user received an eviction notice?") needs_repairs = models.BooleanField( null=True, blank=True, help_text="Does the user need repairs in their apartment?") has_no_services = models.BooleanField( null=True, blank=True, help_text="Is the user missing essential services like water?") has_pests = models.BooleanField( null=True, blank=True, help_text="Does the user have pests like rodents or bed bugs?") has_called_311 = models.BooleanField( null=True, blank=True, help_text="Has the user called 311 before?") lease_type = models.CharField( max_length=30, choices=LEASE_CHOICES.choices, blank=True, help_text="The type of housing the user lives in (NYC only).", ) receives_public_assistance = models.BooleanField( null=True, blank=True, help_text="Does the user receive public assistance, e.g. Section 8?") can_we_sms = models.BooleanField( help_text="Whether we can contact the user via SMS to follow up.") can_rtc_sms = models.BooleanField( help_text= "Whether the Right to Counsel NYC Coalition can contact the user via SMS.", default=False, ) can_hj4a_sms = models.BooleanField( help_text= "Whether Housing Justice for All can contact the user via SMS.", default=False, ) agreed_to_justfix_terms = models.BooleanField( default=False, help_text=("Whether the user has agreed to the JustFix.nyc terms " "of service and privacy policy."), ) agreed_to_norent_terms = models.BooleanField( default=False, help_text=("Whether the user has agreed to the NoRent.org terms " "of service and privacy policy."), ) agreed_to_evictionfree_terms = models.BooleanField( default=False, help_text=("Whether the user has agreed to the Eviction Free terms " "of service and privacy policy."), ) agreed_to_laletterbuilder_terms = models.BooleanField( default=False, help_text=( "Whether the user has agreed to the LA Letter Builder terms " "of service and privacy policy."), ) can_receive_rttc_comms = models.BooleanField( null=True, blank=True, help_text=("Whether the user has opted-in to being contacted by " "the Right to the City Alliance (RTTC)."), ) can_receive_saje_comms = models.BooleanField( null=True, blank=True, help_text=("Whether the user has opted-in to being contacted by " "Strategic Actions for a Just Economy (SAJE)."), ) @property def borough_label(self) -> str: if not self.borough: return "" return BOROUGH_CHOICES.get_label(self.borough) @property def city(self) -> str: """ The city of the user. For NYC-based users, this will be the same as the borough name, except we use "New York" instead of "Manhattan". """ if not self.borough: return self.non_nyc_city if self.borough == BOROUGH_CHOICES.MANHATTAN: return "New York" return self.borough_label @property def full_nyc_address(self) -> str: """Return the full address for purposes of geolocation, etc.""" if not (self.borough and self.address): return "" return f"{self.address}, {self.borough_label}" @property def apartment_address_line(self) -> str: """The address line that specifies the user's apartment number.""" if self.apt_number: return f"Apartment {self.apt_number}" return "" @property def address_lines_for_mailing(self) -> List[str]: """Return the full mailing address as a list of lines.""" result: List[str] = [] if self.address: result.append(self.address) if self.apt_number: result.append(self.apartment_address_line) if self.city: result.append(f"{self.city}, {self.state} {self.zipcode}".strip()) return result @property def address_for_mailing(self) -> str: """Return the full mailing address as a string.""" return "\n".join(self.address_lines_for_mailing) def lookup_county(self) -> Optional[str]: from findhelp.models import County if self.geocoded_point is not None: county = County.objects.filter( state=self.state, geom__contains=self.geocoded_point).first() if county: return county.name return None def as_lob_params(self) -> Dict[str, str]: """ Returns a dictionary representing the address that can be passed directly to Lob's verifications API: https://lob.com/docs#us_verifications_create """ return dict( primary_line=self.address, # TODO: Technically this is the wrong way to use the secondary # line, according to the USPS. We should instead be putting the # apartment number in the primary line. secondary_line=self.apartment_address_line, state=self.state, city=self.city, zip_code=self.zipcode, ) def __str__(self): if not (self.created_at and self.user and self.user.full_legal_name): return super().__str__() return (f"{self.user.full_legal_name}'s onboarding info from " f"{self.created_at.strftime('%A, %B %d %Y')}") def __should_lookup_new_addr_metadata( self, addr: InstanceChangeTracker, addr_meta: InstanceChangeTracker) -> bool: if addr.are_any_fields_blank(): # We can't even look up address metadata without a # full address. return False if addr_meta.are_any_fields_blank(): # We have full address information but no # address metadata, so let's look it up! return True if addr.has_changed() and not addr_meta.has_changed(): # The address information has changed but our address # metadata has not, so let's look it up again. return True return False def lookup_nycaddr_metadata(self): features = geocoding.search(self.full_nyc_address) if features: feature = features[0] props = feature.properties self.geocoded_address = f"{props.label} (via NYC GeoSearch)" self.zipcode = props.postalcode self.pad_bbl = props.pad_bbl self.pad_bin = props.pad_bin self.geometry = feature.geometry.dict() elif self.__nycaddr.has_changed(): # If the address has changed, we really don't want the existing # metadata to be there, because it will represent information # about their old address. self.geocoded_address = "" self.zipcode = "" self.pad_bbl = "" self.pad_bin = "" self.geometry = None self.__nycaddr.set_to_unchanged() self.__nycaddr_meta.set_to_unchanged() def lookup_nationaladdr_metadata(self): # Clear out any NYC-specific metadata. self.pad_bbl = "" self.pad_bin = "" city = self.non_nyc_city addrs = mapbox.find_address( address=self.address, city=city, state=self.state, zip_code=self.zipcode, ) if addrs: addr = addrs[0] self.geometry = addr.geometry.dict() self.geocoded_address = f"{addr.place_name} (via Mapbox)" elif self.__nationaladdr.has_changed(): self.geocoded_address = "" self.geometry = None self.__nationaladdr.set_to_unchanged() self.__nationaladdr_meta.set_to_unchanged() def maybe_lookup_new_addr_metadata(self) -> bool: if self.__should_lookup_new_addr_metadata(self.__nycaddr, self.__nycaddr_meta): self.lookup_nycaddr_metadata() return True if self.__should_lookup_new_addr_metadata(self.__nationaladdr, self.__nationaladdr_meta): self.lookup_nationaladdr_metadata() return True return False def update_geocoded_point_from_geometry(self): """ Set the `geocoded_point` property based on the value of `geometry`. Done automatically on model save. """ if self.geometry is None: self.geocoded_point = None else: self.geocoded_point = GEOSGeometry(json.dumps(self.geometry), srid=4326) def clean(self): if self.borough and self.non_nyc_city: raise ValidationError( "One cannot be in an NYC borough and outside NYC simultaneously." ) def save(self, *args, **kwargs): self.maybe_lookup_new_addr_metadata() self.update_geocoded_point_from_geometry() return super().save(*args, **kwargs) @property def building_links(self) -> List[Hyperlink]: links: List[Hyperlink] = [] if self.pad_bbl: links.append( Hyperlink( name="Who Owns What", url=f"https://whoownswhat.justfix.nyc/bbl/{self.pad_bbl}")) if self.pad_bin: links.append( Hyperlink( name="NYC DOB BIS", url= (f"http://a810-bisweb.nyc.gov/bisweb/PropertyProfileOverviewServlet?" f"bin={self.pad_bin}&go4=+GO+&requestid=0"), )) return links @admin_field(short_description="Building links", allow_tags=True) def get_building_links_html(self) -> str: return Hyperlink.join_admin_buttons(self.building_links)
class Item(models.Model): """Collection item.""" # ******************************************************************* # ******************** Language independent data ******************** # ******************************************************************* importer_uid = models.CharField(max_length=255, verbose_name=_("Importer UID")) record_number = models.CharField(max_length=255, null=True, blank=True, verbose_name=_("Record number"), help_text=_("Record number")) inventory_number = models.CharField( max_length=255, null=True, blank=True, verbose_name=_("Inventory number"), help_text=_("Inventory number in the museum collection")) api_url = models.CharField(max_length=255, verbose_name=_("API item URL"), help_text=_("Link to original data in API")) web_url = models.CharField(max_length=255, null=True, blank=True, verbose_name=_("External item URL"), help_text=_("Link to external data URL")) images = models.ManyToManyField( to='muses_collection.Image', verbose_name=_("Images"), # through='muses_collection.ItemImage' ) geo_location = PointField( null=True, default='', blank=True, verbose_name=_("Geo coordinates"), ) created = models.DateTimeField(auto_now_add=True) updated = models.DateTimeField(auto_now=True) # ******************************************************************* # ******************** Data translated into English ***************** # ******************************************************************* # TODO: Most likely, a suffix (defining the language) shall be added # to all searchable fields. For now leave as is, as we also need to # decide what do we do with the city and country fields. That's one of # the most important parts for now. title_en = models.CharField(max_length=255, null=True, blank=True, verbose_name=_("Title (EN)"), help_text=_("Title of the object")) description_en = models.TextField(null=True, blank=True, verbose_name=_("Description (EN)"), help_text=_("Description of the object")) department_en = models.CharField(max_length=255, null=True, blank=True, verbose_name=_("Department (EN)"), help_text=_("Department of the museum")) dimensions_en = models.TextField(null=True, blank=True, verbose_name=_("Dimensions (EN)"), help_text=_("Dimensions of the object")) city_en = models.CharField( max_length=255, null=True, blank=True, verbose_name=_("City (EN)"), ) object_date_en = models.TextField( null=True, blank=True, verbose_name=_("Object date (EN)"), ) object_date_begin_en = models.CharField( max_length=255, null=True, blank=True, verbose_name=_("Object date begin (EN)"), ) object_date_end_en = models.CharField( max_length=255, null=True, blank=True, verbose_name=_("Object date end (EN)"), ) period_en = models.TextField( null=True, blank=True, verbose_name=_("Period (EN)"), ) # state_province = models.CharField( # max_length=255, # null=True, # blank=True, # verbose_name=_("State/province"), # ) country_en = models.CharField( max_length=255, null=True, blank=True, verbose_name=_("Country (EN)"), ) object_type_en = models.CharField(max_length=255, null=True, blank=True, verbose_name=_("Object type (EN)"), help_text=_("Type of object")) material_en = models.TextField( null=True, blank=True, verbose_name=_("Material (EN)"), help_text=_("Material(s) of which the object is made")) dynasty_en = models.TextField(null=True, blank=True, verbose_name=_("Dynasty (EN)")) references_en = models.TextField( null=True, blank=True, verbose_name=_("References (EN)"), help_text=_( "References to this object in the professional literature")) acquired_en = models.TextField( null=True, blank=True, verbose_name=_("Acquired (EN)"), help_text=_("How the museum acquired the object")) site_found_en = models.CharField( max_length=255, null=True, blank=True, verbose_name=_("Site found (EN)"), help_text=_("Complete record of site where the object was found")) keywords_en = models.TextField(null=True, blank=True, verbose_name=_("Keywords (EN)"), help_text=_("Keywords")) reign_en = models.CharField(max_length=255, null=True, blank=True, verbose_name=_("Reign (EN)"), help_text=_("Reign")) classification_en = models.CharField(max_length=255, null=True, blank=True, verbose_name=_("Classification (EN)"), help_text=_("Classification")) # New fields credit_line_en = models.TextField(null=True, blank=True, verbose_name=_("Credit line (EN)"), help_text=_("Credit line")) region_en = models.TextField(null=True, blank=True, verbose_name=_("Region (EN)"), help_text=_("Region")) sub_region_en = models.TextField(null=True, blank=True, verbose_name=_("Sub region (EN)"), help_text=_("Sub region")) locale_en = models.TextField(null=True, blank=True, verbose_name=_("Locale (EN)"), help_text=_("Locale")) excavation_en = models.TextField(null=True, blank=True, verbose_name=_("Excavation (EN)"), help_text=_("Excavation")) museum_collection_en = models.TextField( null=True, blank=True, verbose_name=_("Museum collection (EN)"), help_text=_("Museum collection")) style_en = models.TextField(null=True, blank=True, verbose_name=_("Style (EN)"), help_text=_("Style")) culture_en = models.TextField(null=True, blank=True, verbose_name=_("Culture (EN)"), help_text=_("Culture")) inscriptions_en = models.TextField(null=True, blank=True, verbose_name=_("Inscriptions (EN)"), help_text=_("Inscriptions")) provenance_en = models.TextField(null=True, blank=True, verbose_name=_("Provenance (EN)"), help_text=_("Provenance")) exhibitions_en = models.TextField(null=True, blank=True, verbose_name=_("Exhibitions (EN)"), help_text=_("Exhibitions")) # ******************************************************************* # ********************* Data translated into Dutch ****************** # ******************************************************************* # TODO: Most likely, a suffix (defining the language) shall be added # to all searchable fields. For now leave as is, as we also need to # decide what do we do with the city and country fields. That's one of # the most important parts for now. title_nl = models.CharField(max_length=255, null=True, blank=True, verbose_name=_("Title (NL)"), help_text=_("Title of the object")) description_nl = models.TextField(null=True, blank=True, verbose_name=_("Description (NL)"), help_text=_("Description of the object")) department_nl = models.CharField(max_length=255, null=True, blank=True, verbose_name=_("Department (NL)"), help_text=_("Department of the museum")) dimensions_nl = models.TextField(null=True, blank=True, verbose_name=_("Dimensions (NL)"), help_text=_("Dimensions of the object")) city_nl = models.CharField( max_length=255, null=True, blank=True, verbose_name=_("City (NL)"), ) object_date_nl = models.TextField( null=True, blank=True, verbose_name=_("Object date (NL)"), ) object_date_begin_nl = models.CharField( max_length=255, null=True, blank=True, verbose_name=_("Object date begin (NL)"), ) object_date_end_nl = models.CharField( max_length=255, null=True, blank=True, verbose_name=_("Object date end (NL)"), ) period_nl = models.TextField( null=True, blank=True, verbose_name=_("Period (NL)"), ) # state_province = models.CharField( # max_length=255, # null=True, # blank=True, # verbose_name=_("State/province"), # ) country_nl = models.CharField( max_length=255, null=True, blank=True, verbose_name=_("Country (NL)"), ) object_type_nl = models.CharField(max_length=255, null=True, blank=True, verbose_name=_("Object type (NL)"), help_text=_("Type of object")) material_nl = models.TextField( null=True, blank=True, verbose_name=_("Material (NL)"), help_text=_("Material(s) of which the object is made")) dynasty_nl = models.TextField(null=True, blank=True, verbose_name=_("Dynasty (NL)")) references_nl = models.TextField( null=True, blank=True, verbose_name=_("References (NL)"), help_text=_( "References to this object in the professional literature")) acquired_nl = models.TextField( null=True, blank=True, verbose_name=_("Acquired (NL)"), help_text=_("How the museum acquired the object")) site_found_nl = models.CharField( max_length=255, null=True, blank=True, verbose_name=_("Site found (NL)"), help_text=_("Complete record of site where the object was found")) keywords_nl = models.TextField(null=True, blank=True, verbose_name=_("Keywords (NL)"), help_text=_("Keywords")) reign_nl = models.CharField(max_length=255, null=True, blank=True, verbose_name=_("Reign (NL)"), help_text=_("Reign")) classification_nl = models.CharField(max_length=255, null=True, blank=True, verbose_name=_("Classification (NL)"), help_text=_("Classification")) # New fields credit_line_nl = models.TextField(null=True, blank=True, verbose_name=_("Credit line (NL)"), help_text=_("Credit line")) region_nl = models.TextField(null=True, blank=True, verbose_name=_("Region (NL)"), help_text=_("Region")) sub_region_nl = models.TextField(null=True, blank=True, verbose_name=_("Sub region (NL)"), help_text=_("Sub region")) locale_nl = models.TextField(null=True, blank=True, verbose_name=_("Locale (NL)"), help_text=_("Locale")) excavation_nl = models.TextField(null=True, blank=True, verbose_name=_("Excavation (NL)"), help_text=_("Excavation")) museum_collection_nl = models.TextField( null=True, blank=True, verbose_name=_("Museum collection (NL)"), help_text=_("Museum collection")) style_nl = models.TextField(null=True, blank=True, verbose_name=_("Style (NL)"), help_text=_("Style")) culture_nl = models.TextField(null=True, blank=True, verbose_name=_("Culture (NL)"), help_text=_("Culture")) inscriptions_nl = models.TextField(null=True, blank=True, verbose_name=_("Inscriptions (NL)"), help_text=_("Inscriptions")) provenance_nl = models.TextField(null=True, blank=True, verbose_name=_("Provenance (NL)"), help_text=_("Provenance")) exhibitions_nl = models.TextField(null=True, blank=True, verbose_name=_("Exhibitions (NL)"), help_text=_("Exhibitions")) # ******************************************************************* # ******************** Original data as imported ******************** # ******************************************************************* language_code_orig = models.CharField( max_length=10, null=True, blank=True, verbose_name=_("Language code of the original")) title_orig = models.CharField(max_length=255, null=True, blank=True, verbose_name=_("Original title"), help_text=_("Title of the object")) description_orig = models.TextField( null=True, blank=True, verbose_name=_("Original description"), help_text=_("Description of the object")) department_orig = models.CharField(max_length=255, null=True, blank=True, verbose_name=_("Original department"), help_text=_("Department of the museum")) dimensions_orig = models.TextField(null=True, blank=True, verbose_name=_("Original dimensions"), help_text=_("Dimensions of the object")) city_orig = models.CharField( max_length=255, null=True, blank=True, verbose_name=_("Original city"), ) object_date_orig = models.TextField( null=True, blank=True, verbose_name=_("Original object date"), ) object_date_begin_orig = models.CharField( max_length=255, null=True, blank=True, verbose_name=_("Original object date begin"), ) object_date_end_orig = models.CharField( max_length=255, null=True, blank=True, verbose_name=_("Original object date end"), ) period_orig = models.TextField( null=True, blank=True, verbose_name=_("Original period"), ) dynasty_orig = models.CharField( max_length=255, null=True, blank=True, verbose_name=_("Original dynasty"), ) # state_province_orig = models.CharField( # max_length=255, # null=True, # blank=True, # verbose_name=_("Original state/province"), # ) country_orig = models.CharField( max_length=255, null=True, blank=True, verbose_name=_("Original country"), ) object_type_orig = models.CharField(max_length=255, null=True, blank=True, verbose_name=_("Original object type"), help_text=_("Type of object")) material_orig = models.TextField( null=True, blank=True, verbose_name=_("Original material"), help_text=_("Material(s) of which the object is made")) references_orig = models.TextField( null=True, blank=True, verbose_name=_("Original references"), help_text=_( "References to this object in the professional literature")) acquired_orig = models.TextField( null=True, blank=True, verbose_name=_("Original acquired"), help_text=_("How the museum acquired the object")) site_found_orig = models.CharField( max_length=255, null=True, blank=True, verbose_name=_("Original site found"), help_text=_("Complete record of site where the object was found")) keywords_orig = models.TextField(null=True, blank=True, verbose_name=_("Keywords"), help_text=_("Keywords")) reign_orig = models.CharField(max_length=255, null=True, blank=True, verbose_name=_("Reign"), help_text=_("Reign")) classification_orig = models.CharField(max_length=255, null=True, blank=True, verbose_name=_("Classification"), help_text=_("Classification")) period_node = TreeForeignKey( 'Period', null=True, blank=True, db_index=True, on_delete=models.SET_NULL, verbose_name=_("Period tree node"), help_text=_("The database period that is related to this object")) classified_as = models.TextField( verbose_name=_("Classified as"), null=True, blank=True, help_text=_("How this object was classified by our AI.")) # New fields credit_line_orig = models.TextField(null=True, blank=True, verbose_name=_("Credit line")) region_orig = models.TextField(null=True, blank=True, verbose_name=_("Region")) sub_region_orig = models.TextField( null=True, blank=True, verbose_name=_("Sub region"), ) locale_orig = models.TextField( null=True, blank=True, verbose_name=_("Locale"), ) excavation_orig = models.TextField( null=True, blank=True, verbose_name=_("Excavation"), ) museum_collection_orig = models.TextField( null=True, blank=True, verbose_name=_("Collection"), ) style_orig = models.TextField( null=True, blank=True, verbose_name=_("Style"), ) culture_orig = models.TextField( null=True, blank=True, verbose_name=_("Culture"), ) inscriptions_orig = models.TextField( null=True, blank=True, verbose_name=_("Inscriptions"), ) provenance_orig = models.TextField( null=True, blank=True, verbose_name=_("Provenance"), ) accession_date = models.DateTimeField( null=True, blank=True, verbose_name=_("Accession date"), ) exhibitions_orig = models.TextField( null=True, blank=True, verbose_name=_("Exhibitions"), ) class Meta(object): """Meta options.""" ordering = ["id"] unique_together = ( ( 'importer_uid', 'record_number', 'inventory_number', # 'api_url', # 'web_url', # 'title_orig', # 'dimensions_orig', # 'period_orig', # 'object_date_begin_orig', # 'object_date_end_orig', # 'object_type_orig', # 'material_orig', ), ) def __str__(self): return str(self.title_orig) def _split_field_value(self, field, delimiters='; |, |\*|\n| ; |;'): """Split field value. :param field: :return: """ if field: split_value = [ _it.strip() for _it in re.split(delimiters, field) if _it.strip() ] if split_value: return split_value else: return [field] def _clean_field_value(self, field): try: clean_field = re.sub(" ?\(?\? ?\)?| \( ?\)|\|.*|\(.*?\)|\(\? \)", "", field).strip() if clean_field: return clean_field.capitalize() \ if clean_field.islower() \ else clean_field else: return field except: return field @property def importer_uid_indexing(self): """Importer UID for indexing.""" return self.importer_uid @property def department_indexing(self): """Department for indexing.""" return self.department_orig @property def classified_as_indexing(self): """Classified as for indexing.""" if self.classified_as: try: classified_as = json.loads(self.classified_as) return [item[0] for item in classified_as][:3] except json.decoder.JSONDecodeError as err: _classified_as = self.classified_as.replace("'", '"') \ .replace('(', '[') \ .replace(')', ']') classified_as = json.loads(_classified_as) return [item[0] for item in classified_as][:3] except Exception as err: pass return [] @property def classified_as_1_indexing(self): """Classified as 1st for indexing.""" if self.classified_as_indexing: return self.classified_as_indexing[0] return VALUE_NOT_SPECIFIED @property def classified_as_2_indexing(self): """Classified as 2nd for indexing.""" if len(self.classified_as_indexing) > 1: return self.classified_as_indexing[1] return VALUE_NOT_SPECIFIED @property def classified_as_3_indexing(self): """Classified as 3rd for indexing.""" if len(self.classified_as_indexing) > 2: return self.classified_as_indexing[2] return VALUE_NOT_SPECIFIED def _period_node_indexing(self, lang): """Period node for indexing.""" counter = 0 tree_dict = {} parent = None if self.period_node: tree = [ '{}_{}'.format( str(el.id).zfill(4), getattr(el, 'name_{}'.format(lang))) for el in self.period_node.get_ancestors(include_self=True) ] tree.pop(0) # if len(tree) > 2: # tree = tree[:2] for counter, item in enumerate(tree): if counter == 0: tree_dict['period_1_{}'.format(lang)] = {'name': item} parent = tree_dict['period_1_{}'.format(lang)] else: parent['period_{}_{}'.format(counter + 1, lang)] = { 'name': item } parent = parent['period_{}_{}'.format(counter + 1, lang)] # return tree_dict if counter > 0: counter += 1 # Filling the missing gaps, since we need that. while counter < 4: if counter == 0: tree_dict['period_1_{}'.format(lang)] = { 'name': VALUE_NOT_SPECIFIED } parent = tree_dict['period_1_{}'.format(lang)] else: parent['period_{}_{}'.format(counter + 1, lang)] = { 'name': VALUE_NOT_SPECIFIED } parent = parent['period_{}_{}'.format(counter + 1, lang)] counter += 1 if tree_dict: return tree_dict['period_1_{}'.format(lang)] return {} # ******************************************************************** # ***************************** English ****************************** # ******************************************************************** @property def department_en_indexing(self): """Department for indexing.""" return self.department_en @property def primary_object_type_en_indexing(self): """Primary object type for indexing.""" return self._clean_field_value(self.object_type_en_indexing[0]) \ if self.object_type_en_indexing \ else VALUE_NOT_SPECIFIED @property def title_en_indexing(self): """Title for indexing.""" return self._split_field_value(self.title_en) @property def object_type_en_indexing(self): """Object type for indexing.""" object_type_en = self._split_field_value(self.object_type_en) if object_type_en is None: object_type_en = [] _classification_en = self._split_field_value(self.classification_en) if _classification_en is None: _classification_en = [] classification_en = [ _item for _item in _classification_en if _item in MET_OBJECT_TYPE_EN_WHITELIST ] object_type_clean = [ self._clean_field_value(val) for val in (object_type_en + classification_en) if val not in ['||', '|'] ] object_type_synonyms = [ OBJECT_TYPE_SYNONYMS_EN[object_type.lower()] for object_type in object_type_clean if object_type.lower() in OBJECT_TYPE_SYNONYMS_EN and OBJECT_TYPE_SYNONYMS_EN[object_type.lower()] != 'Do not use' ] return object_type_synonyms\ if object_type_synonyms \ else VALUE_NOT_SPECIFIED @property def object_type_detail_en_indexing(self): """Object type for indexing. To be shown on detail page.""" object_type_en = self._split_field_value(self.object_type_en) if object_type_en is None: object_type_en = [] classification_en = self._split_field_value(self.classification_en) if classification_en is None: classification_en = [] return object_type_en + classification_en @property def description_en_indexing(self): """Description for indexing.""" if not self.importer_uid == 'brooklynmuseum_org': return self._split_field_value(self.description_en) return VALUE_NOT_SPECIFIED @property def material_en_indexing(self): """Material type for indexing.""" material_en = [] clean = self._clean_field_value(self.material_en) val = self._split_field_value(clean, delimiters='; |, |\*|\n| of') if not val: return VALUE_NOT_SPECIFIED for item in val: match = [ re.search(material, item, flags=re.I) for material in MET_MATERIAL_EN_WHITELIST ] if any(x is not None for x in match): material_en.append(MET_MATERIAL_EN_WHITELIST[next( (i for i, v in enumerate(match) if v is not None), -1)].capitalize()) return material_en @property def material_detail_en_indexing(self): """Material type for indexing. To be shown on detail page.""" clean = self._clean_field_value(self.material_en) val = self._split_field_value(clean, delimiters='; |, |\*|\n| of') if not val: return VALUE_NOT_SPECIFIED return val @property def period_en_indexing(self): """Period for indexing.""" return self._split_field_value(self.period_en) @property def period_1_en_indexing(self): return dict_to_obj(self._period_node_indexing('en')) @property def city_en_indexing(self): """City for indexing.""" clean = self._clean_field_value(self.city_en) val = self._split_field_value( clean, delimiters='; |, |\*|\n| ; | or | and | Or|,|;') if not val: return VALUE_NOT_SPECIFIED return val @property def country_en_indexing(self): """Country for indexing.""" clean = self._clean_field_value(self.country_en) val = self._split_field_value( clean, delimiters='; |, |\*|\n| ; | or | and | Or') if not val: return VALUE_NOT_SPECIFIED return val @property def keywords_en_indexing(self): """Keywords for indexing. To be shown on detail page.""" clean = self._clean_field_value(self.keywords_en) val = self._split_field_value(clean, delimiters='; |, |\*|\n| of') if not val: return VALUE_NOT_SPECIFIED return val @property def acquired_en_indexing(self): """Acquired for indexing. To be shown on detail page.""" if not self.acquired_en: return VALUE_NOT_SPECIFIED return self.acquired_en @property def site_found_en_indexing(self): """Acquired for indexing. To be shown on detail page.""" if not self.site_found_en: return VALUE_NOT_SPECIFIED return self.site_found_en @property def reign_en_indexing(self): """Reign for indexing. To be shown on detail page.""" if not self.reign_en: return VALUE_NOT_SPECIFIED return self.reign_en @property def references_en_indexing(self): """References for indexing. To be shown on detail page.""" if not self.references_orig: return VALUE_NOT_SPECIFIED return self.references_orig @property def dynasty_en_indexing(self): """Dynasty for indexing. To be shown on detail page.""" if not self.dynasty_en: return VALUE_NOT_SPECIFIED return self.dynasty_en # New fields @property def credit_line_en_indexing(self): """credit_line_en for indexing. To be shown on detail page.""" if not self.credit_line_en: return VALUE_NOT_SPECIFIED return self.credit_line_en @property def region_en_indexing(self): """region_en for indexing. To be shown on detail page.""" if not self.region_en: return VALUE_NOT_SPECIFIED return self.region_en @property def sub_region_en_indexing(self): """sub_region_en for indexing. To be shown on detail page.""" if not self.sub_region_en: return VALUE_NOT_SPECIFIED return self.sub_region_en @property def locale_en_indexing(self): """locale_en for indexing. To be shown on detail page.""" if not self.locale_en: return VALUE_NOT_SPECIFIED return self.locale_en @property def locus_en_indexing(self): """locus_en for indexing. To be shown on detail page.""" if self.importer_uid == 'brooklynmuseum_org': if self.description_en: return self.description_en return VALUE_NOT_SPECIFIED @property def excavation_en_indexing(self): """excavation_en for indexing. To be shown on detail page.""" if not self.excavation_en: return VALUE_NOT_SPECIFIED return self.excavation_en @property def museum_collection_en_indexing(self): """museum_collection_en for indexing. To be shown on detail page.""" if not self.museum_collection_en: return VALUE_NOT_SPECIFIED return self.museum_collection_en @property def style_en_indexing(self): """style_en for indexing. To be shown on detail page.""" if not self.style_en: return VALUE_NOT_SPECIFIED return self.style_en @property def culture_en_indexing(self): """Dynasty for indexing. To be shown on detail page.""" if not self.culture_en: return VALUE_NOT_SPECIFIED return self.culture_en @property def inscriptions_en_indexing(self): """inscriptions_en for indexing. To be shown on detail page.""" if not self.inscriptions_en: return VALUE_NOT_SPECIFIED return self.inscriptions_en @property def provenance_en_indexing(self): """provenance_en for indexing. To be shown on detail page.""" if not self.provenance_en: return VALUE_NOT_SPECIFIED return self.provenance_en @property def exhibitions_en_indexing(self): """exhibitions_en for indexing. To be shown on detail page.""" if not self.exhibitions_en: return VALUE_NOT_SPECIFIED return self._split_field_value(self.exhibitions_en) # ******************************************************************** # ****************************** Dutch ******************************* # ******************************************************************** @property def department_nl_indexing(self): """Department for indexing.""" return self.department_nl @property def primary_object_type_nl_indexing(self): """Primary object type for indexing.""" return self._clean_field_value(self.object_type_nl_indexing[0]) \ if self.object_type_nl_indexing \ else VALUE_NOT_SPECIFIED @property def title_nl_indexing(self): """Title for indexing.""" return self._split_field_value(self.title_nl) @property def object_type_nl_indexing(self): """Object type for indexing.""" object_type_nl = self._split_field_value(self.object_type_nl) if object_type_nl is None: object_type_nl = [] _classification_nl = self._split_field_value(self.classification_nl) if _classification_nl is None: _classification_nl = [] classification_nl = [ _item for _item in _classification_nl if _item in MET_OBJECT_TYPE_NL_WHITELIST ] object_type_clean = [ self._clean_field_value(val) for val in (object_type_nl + classification_nl) if val not in ['||', '|'] ] object_type_synonyms = [ OBJECT_TYPE_SYNONYMS_NL[object_type.lower()] for object_type in object_type_clean if object_type.lower() in OBJECT_TYPE_SYNONYMS_NL and OBJECT_TYPE_SYNONYMS_NL[object_type.lower()] != 'Do not use' ] return object_type_synonyms \ if object_type_synonyms \ else VALUE_NOT_SPECIFIED @property def object_type_detail_nl_indexing(self): """Object type for indexing. To be shown on detail page.""" object_type_nl = self._split_field_value(self.object_type_nl) if object_type_nl is None: object_type_nl = [] classification_nl = self._split_field_value(self.classification_nl) if classification_nl is None: classification_nl = [] return object_type_nl + classification_nl @property def description_nl_indexing(self): """Description for indexing.""" return self._split_field_value(self.description_nl) @property def material_nl_indexing(self): """Material type for indexing.""" material_nl = [] clean = self._clean_field_value(self.material_nl) val = self._split_field_value(clean, delimiters='; |, |\*|\n| of') if not val: return VALUE_NOT_SPECIFIED for item in val: match = [ re.search(material, item, flags=re.I) for material in MET_MATERIAL_NL_WHITELIST ] if any(x is not None for x in match): material_nl.append(MET_MATERIAL_NL_WHITELIST[next( (i for i, v in enumerate(match) if v is not None), -1)].capitalize()) return material_nl @property def material_detail_nl_indexing(self): """Material type for indexing. To be shown on detail page.""" clean = self._clean_field_value(self.material_nl) val = self._split_field_value(clean, delimiters='; |, |\*|\n| of') if not val: return VALUE_NOT_SPECIFIED return val @property def period_nl_indexing(self): """Period for indexing.""" return self._split_field_value(self.period_nl) @property def period_1_nl_indexing(self): return dict_to_obj(self._period_node_indexing('nl')) @property def city_nl_indexing(self): """City for indexing.""" clean = self._clean_field_value(self.city_nl) val = self._split_field_value( clean, delimiters='; |, |\*|\n| ; | of | en | Of|,|;') if not val: return VALUE_NOT_SPECIFIED return val @property def country_nl_indexing(self): """Country for indexing.""" clean = self._clean_field_value(self.country_nl) val = self._split_field_value( clean, delimiters='; |, |\*|\n| ; | of | en | Of') if not val: return VALUE_NOT_SPECIFIED return val @property def keywords_nl_indexing(self): """Keywords for indexing. To be shown on detail page.""" clean = self._clean_field_value(self.keywords_nl) val = self._split_field_value(clean, delimiters='; |, |\*|\n| of') if not val: return VALUE_NOT_SPECIFIED return val @property def acquired_nl_indexing(self): """Acquired for indexing. To be shown on detail page.""" if not self.acquired_nl: return VALUE_NOT_SPECIFIED return self.acquired_nl @property def site_found_nl_indexing(self): """Acquired for indexing. To be shown on detail page.""" if not self.site_found_nl: return VALUE_NOT_SPECIFIED return self.site_found_nl @property def reign_nl_indexing(self): """Reign for indexing. To be shown on detail page.""" if not self.reign_nl: return VALUE_NOT_SPECIFIED return self.reign_nl @property def references_nl_indexing(self): """References for indexing. To be shown on detail page.""" if not self.references_orig: return VALUE_NOT_SPECIFIED return self.references_orig @property def dynasty_nl_indexing(self): """Dynasty for indexing. To be shown on detail page.""" if not self.dynasty_nl: return VALUE_NOT_SPECIFIED return self.dynasty_nl # New fields @property def credit_line_nl_indexing(self): """credit_line_nl for indexing. To be shown on detail page.""" if not self.credit_line_nl: return VALUE_NOT_SPECIFIED return self.credit_line_nl @property def region_nl_indexing(self): """region_nl for indexing. To be shown on detail page.""" if not self.region_nl: return VALUE_NOT_SPECIFIED return self.region_nl @property def sub_region_nl_indexing(self): """sub_region_nl for indexing. To be shown on detail page.""" if not self.sub_region_nl: return VALUE_NOT_SPECIFIED return self.sub_region_nl @property def locale_nl_indexing(self): """locale_nl for indexing. To be shown on detail page.""" if not self.locale_nl: return VALUE_NOT_SPECIFIED return self.locale_nl @property def excavation_nl_indexing(self): """excavation_nl for indexing. To be shown on detail page.""" if not self.excavation_nl: return VALUE_NOT_SPECIFIED return self.excavation_nl @property def museum_collection_nl_indexing(self): """museum_collection_nl for indexing. To be shown on detail page.""" if not self.museum_collection_nl: return VALUE_NOT_SPECIFIED return self.museum_collection_nl @property def style_nl_indexing(self): """style_nl for indexing. To be shown on detail page.""" if not self.style_nl: return VALUE_NOT_SPECIFIED return self.style_nl @property def culture_nl_indexing(self): """culture_nl for indexing. To be shown on detail page.""" if not self.culture_nl: return VALUE_NOT_SPECIFIED return self.culture_nl @property def inscriptions_nl_indexing(self): """inscriptions_nl for indexing. To be shown on detail page.""" if not self.inscriptions_nl: return VALUE_NOT_SPECIFIED return self.inscriptions_nl @property def provenance_nl_indexing(self): """provenance_nl for indexing. To be shown on detail page.""" if not self.provenance_nl: return VALUE_NOT_SPECIFIED return self.provenance_nl @property def exhibitions_nl_indexing(self): """exhibitions_nl for indexing. To be shown on detail page.""" if not self.exhibitions_nl: return VALUE_NOT_SPECIFIED return self._split_field_value(self.exhibitions_nl) # ******************************************************************** # ************************** Language independent ******************** # ******************************************************************** @property def object_date_indexing(self): """Object date for indexing.""" return self._split_field_value(self.object_date_orig) @property def object_date_begin_indexing(self): """Object date begin for indexing.""" return self.object_date_begin_orig @property def object_date_end_indexing(self): """Object date for indexing.""" return self.object_date_end_orig @property def dimensions_indexing(self): """Dimensions for indexing.""" return self.dimensions_orig @property def images_indexing(self): """Images (used in indexing). :return: """ val = [] for _im in self.images \ .all() \ .filter(active=True) \ .order_by('-primary', 'created'): try: if _im.image and _im.image.url: val.append(_im.image_ml.path) except IOError as err: LOGGER.error(err) LOGGER.error("Image details: id {}".format(_im.id)) return val @property def images_urls_indexing(self): """Images URLs (used in indexing). :return: """ val = [] for _im in self.images \ .all() \ .filter(active=True) \ .order_by('-primary', 'created'): try: if _im.image and _im.image.url: val.append({ 'th': get_media_url(_im.image_sized.path), 'lr': get_media_url(_im.image_large.path), }) except IOError as err: LOGGER.error(err) LOGGER.error("Image details: id {}".format(_im.id)) if val: return val return [{}] @property def coordinates(self): """Coordinates usable on the map.""" if self.geo_location: lat, lng = self.geo_location.tuple return str(lat), str(lng) return None @property def coordinates_str(self): """Coordinates string.""" lat, lng = self.coordinates if lat and lng: return "{0},{1}".format(lat, lng) @property def geo_location_indexing(self): """Location for indexing. Used in Elasticsearch indexing/tests of `geo_distance` native filter. """ coordinates = self.coordinates lat, lon = DEFAULT_LATITUDE, DEFAULT_LONGITUDE if coordinates: lat, lon = coordinates return { 'lat': lat, 'lon': lon, }
class Place(TrackingModel, TimeStampedModel): owner = models.ForeignKey( 'hosting.Profile', verbose_name=_("owner"), related_name="owned_places", on_delete=models.CASCADE) address = models.TextField( _("address"), blank=True, help_text=_("e.g.: Nieuwe Binnenweg 176")) city = models.CharField( _("city"), blank=True, max_length=255, validators=[validate_not_all_caps, validate_not_too_many_caps], help_text=_("Name in the official language, not in Esperanto (e.g.: Rotterdam)")) closest_city = models.CharField( _("closest big city"), blank=True, max_length=255, validators=[validate_not_all_caps, validate_not_too_many_caps], help_text=_("If your place is in a town near a bigger city. " "Name in the official language, not in Esperanto.")) postcode = models.CharField( _("postcode"), blank=True, max_length=11) state_province = models.CharField( _("State / Province"), blank=True, max_length=70) country = CountryField( _("country")) location = PointField( _("location"), srid=4326, null=True, blank=True) latitude = models.FloatField( _("latitude"), null=True, blank=True) longitude = models.FloatField( _("longitude"), null=True, blank=True) max_guest = models.PositiveSmallIntegerField( _("maximum number of guest"), null=True, blank=True) max_night = models.PositiveSmallIntegerField( _("maximum number of night"), null=True, blank=True) contact_before = models.PositiveSmallIntegerField( _("contact before"), null=True, blank=True, help_text=_("Number of days before people should contact host.")) description = models.TextField( _("description"), blank=True, help_text=_("Description or remarks about your place.")) short_description = models.CharField( _("short description"), blank=True, max_length=140, help_text=_("Used in the book and on profile, 140 characters maximum.")) available = models.BooleanField( _("available"), default=True, help_text=_("If this place is searchable. If yes, you will be considered as host.")) in_book = models.BooleanField( _("print in book"), default=True, help_text=_("If you want this place to be in the printed book. Must be available.")) tour_guide = models.BooleanField( _("tour guide"), default=False, help_text=_("If you are ready to show your area to visitors.")) have_a_drink = models.BooleanField( _("have a drink"), default=False, help_text=_("If you are ready to have a coffee or beer with visitors.")) sporadic_presence = models.BooleanField( _("irregularly present"), default=False, help_text=_("If you are not often at this address and need an advance notification.")) conditions = models.ManyToManyField( 'hosting.Condition', verbose_name=_("conditions"), blank=True) family_members = models.ManyToManyField( 'hosting.Profile', verbose_name=_("family members"), blank=True) family_members_visibility = models.OneToOneField( 'hosting.VisibilitySettingsForFamilyMembers', related_name='family_members', on_delete=models.PROTECT) blocked_from = models.DateField( _("unavailable from"), null=True, blank=True, help_text=_("In the format year(4 digits)-month(2 digits)-day(2 digits).")) blocked_until = models.DateField( _("unavailable until"), null=True, blank=True, help_text=_("In the format year(4 digits)-month(2 digits)-day(2 digits).")) authorized_users = models.ManyToManyField( settings.AUTH_USER_MODEL, verbose_name=_("authorized users"), blank=True, help_text=_("List of users authorized to view most of data of this accommodation.")) visibility = models.OneToOneField( 'hosting.VisibilitySettingsForPlace', related_name='%(class)s', on_delete=models.PROTECT) available_objects = AvailableManager() class Meta: verbose_name = _("place") verbose_name_plural = _("places") default_manager_name = 'all_objects' @property def profile(self): """Proxy for self.owner. Rename 'owner' to 'profile' if/as possible.""" return self.owner @property def lat(self): if not self.location or self.location.empty: return 0 return round(self.location.y, 2) @property def lng(self): if not self.location or self.location.empty: return 0 return round(self.location.x, 2) @property def bbox(self): """Return an OpenStreetMap formated bounding box. See http://wiki.osm.org/wiki/Bounding_Box """ dx, dy = 0.007, 0.003 # Delta lng and delta lat around position boundingbox = (self.lng - dx, self.lat - dy, self.lng + dx, self.lat + dy) return ",".join([str(coord) for coord in boundingbox]) @property def icon(self): template = ('<span class="fa ps-home-fh" title="{title}" ' ' data-toggle="tooltip" data-placement="left"></span>') return format_html(template, title=self._meta.verbose_name.capitalize()) def family_members_cache(self): """ Cached QuerySet of family members. (Direct access to the field in templates re-queries the database.) """ return self.__dict__.setdefault('_family_cache', self.family_members.order_by('birth_date')) @property def family_is_anonymous(self): """ Returns True when there is only one family member, which does not have a name and is not a user of the website (profile without user account). """ family = self.family_members_cache() return len(family) == 1 and not family[0].user_id and not family[0].full_name def authorized_users_cache(self, complete=True, also_deleted=False): """ Cached QuerySet of authorized users. (Direct access to the field in templates re-queries the database.) - Flag `complete` fetches also profile data for each user record. - Flag `also_deleted` fetches records with deleted_on != NULL. """ cache_name = '_authed{}{}_cache'.format( '_all' if also_deleted else '_active', '_complete' if complete else '', ) try: cached_qs = self.__dict__[cache_name] except KeyError: cached_qs = self.authorized_users.all() if not also_deleted: cached_qs = cached_qs.filter(profile__deleted_on__isnull=True) if complete: cached_qs = cached_qs.select_related('profile').defer('profile__description') self.__dict__[cache_name] = cached_qs finally: return cached_qs def conditions_cache(self): """ Cached QuerySet of place conditions. (Direct access to the field in templates re-queries the database.) """ return self.__dict__.setdefault('_conditions_cache', self.conditions.all()) @property def owner_available(self): return self.tour_guide or self.have_a_drink @property def is_blocked(self): return any([self.blocked_until and self.blocked_until >= date.today(), self.blocked_from and not self.blocked_until]) def get_absolute_url(self): return reverse('place_detail', kwargs={'pk': self.pk}) def get_locality_display(self): """ Returns "city (country)" or just "country" when no city is given. """ if self.city: return format_lazy("{city} ({state})", city=self.city, state=self.country.name) else: return self.country.name def __str__(self): return ", ".join([self.city, str(self.country.name)]) if self.city else str(self.country.name) def __repr__(self): return "<{} #{}: {}>".format(self.__class__.__name__, self.id, self.__str__()) def rawdisplay_family_members(self): family_members = self.family_members.exclude(pk=self.owner_id).order_by('birth_date') return ", ".join(fm.rawdisplay() for fm in family_members) def rawdisplay_conditions(self): return ", ".join(c.__str__() for c in self.conditions.all()) def latexdisplay_conditions(self): return r"\, ".join(c.latex for c in self.conditions.all()) # GeoJSON properties @property def url(self): return self.get_absolute_url() @property def owner_name(self): return self.owner.name or self.owner.INCOGNITO @property def owner_url(self): return self.owner.get_absolute_url()