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)
Beispiel #2
0
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
Beispiel #3
0
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)
Beispiel #4
0
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)
Beispiel #5
0
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()
Beispiel #6
0
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']
Beispiel #7
0
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', )), )
Beispiel #9
0
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()
Beispiel #11
0
class Record(models.Model):
    location = PointField()

    title = models.CharField(max_length=30)
    created = models.DateTimeField(auto_now_add=True)
Beispiel #12
0
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 = 'Населенные пункты'
Beispiel #13
0
class AttributeTypeGeoposition(AttributeTypeBase):
    attr_name = "Geoposition"
    dtype = "geopos"
    default = PointField(null=True, blank=True)
Beispiel #14
0
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])
Beispiel #16
0
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)
Beispiel #17
0
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()
Beispiel #18
0
    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')
Beispiel #19
0
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})
Beispiel #20
0
class Building(models.Model):
    address = models.CharField(max_length=75, unique=True)
    coordinates = PointField(geography=True, unique=True)

    class Meta:
        ordering = ('address', )
Beispiel #21
0
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
Beispiel #22
0
class User(Model):
     location = PointField(null=True, spatial_index=True)
     
     class Meta:
     	AbstractUser
Beispiel #23
0
class Location(models.Model):
    point = PointField(srid=DEFAULT_SRID, null=True, blank=True)
Beispiel #24
0
class Place(Model):
    city = ForeignKey('City')
    name = TextField()
    point = PointField()
Beispiel #25
0
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
Beispiel #26
0
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)
Beispiel #28
0
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)
Beispiel #29
0
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
Beispiel #30
0
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)
Beispiel #31
0
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,
        }
Beispiel #32
0
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()