예제 #1
0
class Node(BaseAccessLevel):
    """
    Nodes are generic geo-referenced records
    Can be assigned to 'Layers' if nodeshot.core.layers is installed
    Can belong to 'Users'
    """
    name = models.CharField(_('name'), max_length=75, unique=True)
    slug = models.SlugField(max_length=75, db_index=True, unique=True, blank=True)
    status = models.ForeignKey(Status, blank=True, null=True)
    is_published = models.BooleanField(default=PUBLISHED_DEFAULT)

    # TODO: find a way to move this in layers
    if 'nodeshot.core.layers' in settings.INSTALLED_APPS:
        # layer might need to be able to be blank, would require custom validation
        layer = models.ForeignKey('layers.Layer')

    # owner, allow NULL
    user = models.ForeignKey(settings.AUTH_USER_MODEL, blank=True, null=True)

    # geographic information
    geometry = models.GeometryField(
        _('geometry'),
        help_text=_('geometry of the node (point, polygon, line)')
    )
    elev = models.FloatField(_('elevation'), blank=True, null=True)
    address = models.CharField(_('address'), max_length=150, blank=True, null=True)

    # descriptive information
    description = models.TextField(_('description'), max_length=255, blank=True, null=True)
    notes = models.TextField(_('notes'), blank=True, null=True,
                             help_text=_('for internal use only'))

    data = DictionaryField(_('extra data'), null=True, blank=True,
                           editable=False, schema=HSTORE_SCHEMA)

    # manager
    objects = NodeManager()

    # this is needed to check if the status is changing
    # explained here:
    # http://stackoverflow.com/questions/1355150/django-when-saving-how-can-you-check-if-a-field-has-changed
    _current_status = None

    # needed for extensible validation
    _additional_validation = []

    class Meta:
        db_table = 'nodes_node'
        app_label = 'nodes'

    def __unicode__(self):
        return '%s' % self.name

    def __init__(self, *args, **kwargs):
        """ Fill __current_status """
        super(Node, self).__init__(*args, **kwargs)
        # set current status, but only if it is an existing node
        if self.pk:
            self._current_status = self.status_id

    def _autofill_slug(self):
        slugified_name = slugify(self.name)
        # auto generate slug
        if not self.slug or self.slug != slugified_name:
            self.slug = slugified_name

    def clean(self, *args, **kwargs):
        """ call extensible validation """
        self._autofill_slug()
        self.extensible_validation()

    def save(self, *args, **kwargs):
        """
        Custom save method does the following things:
            * converts geometry collections of just 1 item to that item (eg: a collection of 1 Point becomes a Point)
            * intercepts changes to status and fires node_status_changed signal
            * set default status
        """
        # geometry collection check
        if isinstance(self.geometry, GeometryCollection) and 0 < len(self.geometry) < 2:
            self.geometry = self.geometry[0]
        # if no status specified
        if not self.status and not self.status_id:
            try:
                self.status = Status.objects.filter(is_default=True)[0]
            except IndexError:
                pass
        super(Node, self).save(*args, **kwargs)
        # if status of a node changes
        if (self.status and self._current_status and self.status.id != self._current_status) or\
           (self.status_id and self._current_status and self.status_id != self._current_status):
            # send django signal
            node_status_changed.send(
                sender=self.__class__,
                instance=self,
                old_status=Status.objects.get(pk=self._current_status),
                new_status=self.status
            )
        # update _current_status
        self._current_status = self.status_id

    def extensible_validation(self):
        """
        Execute additional validation that might be defined elsewhere in the code.
        Additional validation is introduced through the class method Node.add_validation_method()
        """
        # loop over additional validation method list
        for validation_method in self._additional_validation:
            # call each additional validation method
            getattr(self, validation_method)()

    @classmethod
    def add_validation_method(cls, method):
        """
        Extend validation of Node by adding a function to the _additional_validation list.
        The additional validation function will be called by the clean method

        :method function: function to be added to _additional_validation
        """
        method_name = method.func_name

        # add method name to additional validation method list
        cls._additional_validation.append(method_name)

        # add method to this class
        setattr(cls, method_name, method)

    @property
    def owner(self):
        return self.user

    @property
    def point(self):
        """ returns location of node. If node geometry is not a point a center  point will be returned """
        if not self.geometry:
            raise ValueError('geometry attribute must be set before trying to get point property')
        if self.geometry.geom_type == 'Point':
            return self.geometry
        else:
            try:
                # point_on_surface guarantees that the point is within the geometry
                return self.geometry.point_on_surface
            except GEOSException:
                # fall back on centroid which may not be within the geometry
                # for example, a horseshoe shaped polygon
                return self.geometry.centroid

    if 'grappelli' in settings.INSTALLED_APPS:
        @staticmethod
        def autocomplete_search_fields():
            return ('name__icontains', 'slug__icontains', 'address__icontains')
예제 #2
0
class Place(MPTTModel, BaseModel, SchemalessFieldMixin, ImageMixin, ReplacedByMixin):
    objects = BaseTreeQuerySet.as_manager()
    geo_objects = objects

    publisher = models.ForeignKey(
        'django_orghierarchy.Organization', on_delete=models.CASCADE, verbose_name=_('Publisher'), db_index=True)
    info_url = models.URLField(verbose_name=_('Place home page'), null=True, blank=True, max_length=1000)
    description = models.TextField(verbose_name=_('Description'), null=True, blank=True)
    parent = TreeForeignKey('self', on_delete=models.CASCADE, null=True, blank=True,
                            related_name='children')

    position = models.PointField(srid=settings.PROJECTION_SRID, null=True,
                                 blank=True)

    email = models.EmailField(verbose_name=_('E-mail'), null=True, blank=True)
    telephone = models.CharField(verbose_name=_('Telephone'), max_length=128, null=True, blank=True)
    contact_type = models.CharField(verbose_name=_('Contact type'), max_length=255, null=True, blank=True)
    street_address = models.CharField(verbose_name=_('Street address'), max_length=255, null=True, blank=True)
    address_locality = models.CharField(verbose_name=_('Address locality'), max_length=255, null=True, blank=True)
    address_region = models.CharField(verbose_name=_('Address region'), max_length=255, null=True, blank=True)
    postal_code = models.CharField(verbose_name=_('Postal code'), max_length=128, null=True, blank=True)
    post_office_box_num = models.CharField(verbose_name=_('PO BOX'), max_length=128, null=True,
                                           blank=True)
    address_country = models.CharField(verbose_name=_('Country'), max_length=2, null=True, blank=True)

    deleted = models.BooleanField(verbose_name=_('Deleted'), default=False)
    replaced_by = models.ForeignKey('Place', on_delete=models.SET_NULL, related_name='aliases', null=True, blank=True)
    divisions = models.ManyToManyField(AdministrativeDivision, verbose_name=_('Divisions'), related_name='places',
                                       blank=True)
    n_events = models.IntegerField(
        verbose_name=_('event count'),
        help_text=_('number of events in this location'),
        default=0,
        editable=False,
        db_index=True
    )
    n_events_changed = models.BooleanField(default=False, db_index=True)

    class Meta:
        verbose_name = _('place')
        verbose_name_plural = _('places')
        unique_together = (('data_source', 'origin_id'),)

    def __unicode__(self):
        values = filter(lambda x: x, [
            self.street_address, self.postal_code, self.address_locality
        ])
        return u', '.join(values)

    @transaction.atomic
    def save(self, *args, **kwargs):
        if self._has_circular_replacement():
            raise ValidationError(_("Trying to replace this place with a place that is replaced by this place. "
                                    "Please refrain from creating circular replacements and remove one of the "
                                    "replacements. We don't want homeless events."))

        if self.replaced_by and not self.deleted:
            self.deleted = True
            logger.warning("Place replaced without soft deleting. Soft deleting automatically", extra={'place': self})

        # needed to remap events to replaced location
        old_replaced_by = None
        if self.id:
            try:
                old_replaced_by = Place.objects.get(id=self.id).replaced_by
            except Place.DoesNotExist:
                pass

        super().save(*args, **kwargs)

        # needed to remap events to replaced location
        if not old_replaced_by == self.replaced_by:
            Event.objects.filter(location=self).update(location=self.replaced_by)
            # Update doesn't call save so we update event numbers manually.
            # Not all of the below are necessarily present.
            ids_to_update = [event.id for event in (self, self.replaced_by, old_replaced_by) if event]
            Place.objects.filter(id__in=ids_to_update).update(n_events_changed=True)

        if self.position:
            self.divisions.set(AdministrativeDivision.objects.filter(
                type__type__in=('district', 'sub_district', 'neighborhood', 'muni'),
                geometry__boundary__contains=self.position))
        else:
            self.divisions.clear()
예제 #3
0
class CircularAreaGeom(models.Model):
    geomValue = models.TextField()
예제 #4
0
class Image(models.Model):
    url = models.TextField()
    layer = models.ForeignKey(Layer, blank=True, null=True)
    file_size = models.IntegerField(blank=True,null=True)
    file_format = models.CharField(max_length=255, blank=True,null=True)
    crs = models.CharField(max_length=100, blank=True, null=True)
    bbox = models.CharField(max_length=255)
    width = models.IntegerField()
    height = models.IntegerField()
    hash = models.CharField(max_length=100, blank=True, null=True)
    license = models.ForeignKey(License, blank=True, null=True)
    attribution = models.ForeignKey(Attribution, blank=True, null=True)
    vrt = models.TextField(blank=True, null=True)
    vrt_date = models.DateTimeField(blank=True,null=True)
    archive = models.BooleanField(default=True)
    owner = models.ForeignKey(User)
    def from_json(self, data):
        required_keys = ['url', 'width', 'height']
        optional_keys = ['file_size', 'file_format', 'hash', 'crs', 'vrt', 'archive']
        errors = []
        warnings = []
        for key in required_keys:
            if key in data:
                setattr(self, key, data[key])
            elif getattr(self, key) == None:
                errors.append("No %s provided for image." % key)
        for key in optional_keys:
            if key in data:
                setattr(self, key, data[key])
            else:
                warnings.append("Missing %s in image data. This is a recommended field." % key)
        if 'layer' in data:
            errors.append("No layer handling available at this time. Please upload images without a Layer identifier.")
        if 'bbox' in data:
            self.bbox = ",".join(map(str,data['bbox']))
        else:
            errors.append("No BBOX provided for image.")
        if 'archive' in data:
            self.archive = data['archive']
        if not 'license' in data:
            errors.append("No license ID was passed")
        elif isinstance(data['license'], int):
            self.license = License.objects.get(pk=data['license'])
        elif isinstance(data['license'], dict):
             l = License()
             l.from_json(data['license'])
             l.save()
             self.license = l
        else:
            errors.append("Some license information is required.")
        if errors:
            raise ApplicationError(errors)
        self.vrt_date = None
        self.owner = User.objects.get(pk=1)
        self.save()
        return self
    def to_json(self):
        return {
            'id': self.id,
            'url': self.url,
            'file_size': self.file_size,
            'file_format': self.file_format,
            'crs': self.crs,
            'bbox': map(float, self.bbox.split(",")),
            'width': self.width,
            'height': self.height,
            'hash': self.hash,
            'archive': self.archive,
            'license': self.license.to_json(),
            'vrt': self.vrt,
            'vrt_date': self.vrt_date
        }    
예제 #5
0
class Parcel(models.Model):
    short_pin = models.CharField(max_length=100, blank=True, null=True)
    zoning = models.CharField(max_length=100, blank=True, null=True)
    pre_percent = models.DecimalField(max_digits=65535,
                                      decimal_places=65535,
                                      blank=True,
                                      null=True)
    pre_value = models.DecimalField(max_digits=65535,
                                    decimal_places=65535,
                                    blank=True,
                                    null=True)
    eq_value = models.DecimalField(max_digits=65535,
                                   decimal_places=65535,
                                   blank=True,
                                   null=True)
    tent_value = models.DecimalField(max_digits=65535,
                                     decimal_places=65535,
                                     blank=True,
                                     null=True)
    acreage_of_parent = models.DecimalField(max_digits=65535,
                                            decimal_places=65535,
                                            blank=True,
                                            null=True)
    assessed_value = models.DecimalField(max_digits=65535,
                                         decimal_places=65535,
                                         blank=True,
                                         null=True)
    building_assessment = models.DecimalField(max_digits=65535,
                                              decimal_places=65535,
                                              blank=True,
                                              null=True)
    capped_value = models.DecimalField(max_digits=65535,
                                       decimal_places=65535,
                                       blank=True,
                                       null=True)
    cvt_code = models.CharField(max_length=10, blank=True, null=True)
    cvt_description = models.CharField(max_length=40, blank=True, null=True)
    historical_district = models.CharField(max_length=4, blank=True, null=True)
    homestead_taxable = models.DecimalField(max_digits=65535,
                                            decimal_places=65535,
                                            blank=True,
                                            null=True)
    homestead_percent = models.DecimalField(max_digits=65535,
                                            decimal_places=65535,
                                            blank=True,
                                            null=True)
    lastupdate = models.DecimalField(max_digits=65535,
                                     decimal_places=65535,
                                     blank=True,
                                     null=True)
    legal_description = models.TextField(blank=True, null=True)
    owner_care_of = models.CharField(max_length=100, blank=True, null=True)
    owner_city = models.CharField(max_length=100, blank=True, null=True)
    owner_country = models.CharField(max_length=100, blank=True, null=True)
    owner_name = models.CharField(max_length=100, blank=True, null=True)
    owner_name2 = models.CharField(max_length=100, blank=True, null=True)
    owner_state = models.CharField(max_length=2, blank=True, null=True)
    owner_street = models.CharField(max_length=100, blank=True, null=True)
    owner_zip = models.CharField(max_length=20, blank=True, null=True)
    parent_parcel_num = models.CharField(max_length=40, blank=True, null=True)
    pin = models.CharField(max_length=40, blank=True, null=True)
    prop_city = models.CharField(max_length=100, blank=True, null=True)
    prop_class = models.CharField(max_length=10, blank=True, null=True)
    prop_class_description = models.CharField(max_length=100,
                                              blank=True,
                                              null=True)
    prop_state = models.CharField(max_length=2, blank=True, null=True)
    prop_street = models.CharField(max_length=100, blank=True, null=True)
    prop_street_num = models.DecimalField(max_digits=65535,
                                          decimal_places=65535,
                                          blank=True,
                                          null=True)
    prop_zip = models.CharField(max_length=20, blank=True, null=True)
    school_district = models.CharField(max_length=100, blank=True, null=True)
    school_name = models.CharField(max_length=100, blank=True, null=True)
    sev = models.DecimalField(max_digits=65535,
                              decimal_places=65535,
                              blank=True,
                              null=True)
    shape_area = models.DecimalField(max_digits=65535,
                                     decimal_places=65535,
                                     blank=True,
                                     null=True)
    shape_len = models.DecimalField(max_digits=65535,
                                    decimal_places=65535,
                                    blank=True,
                                    null=True)
    stated_area = models.CharField(max_length=20, blank=True, null=True)
    status_code = models.CharField(max_length=4, blank=True, null=True)
    status_desc = models.CharField(max_length=20, blank=True, null=True)
    taxable_value = models.DecimalField(max_digits=65535,
                                        decimal_places=65535,
                                        blank=True,
                                        null=True)
    txpyrs_care_of = models.CharField(max_length=100, blank=True, null=True)
    txpyrs_city = models.CharField(max_length=100, blank=True, null=True)
    txpyrs_country = models.CharField(max_length=100, blank=True, null=True)
    txpyrs_name = models.CharField(max_length=100, blank=True, null=True)
    txpyrs_state = models.CharField(max_length=100, blank=True, null=True)
    txpyrs_street_addr = models.CharField(max_length=100,
                                          blank=True,
                                          null=True)
    txpyrs_zip_code = models.CharField(max_length=20, blank=True, null=True)
    unit_apt_num = models.CharField(max_length=20, blank=True, null=True)
    geometry = models.TextField(blank=True, null=True)
    geom = models.MultiPolygonField(srid=4326, blank=True, null=True)
    centroid = models.PointField()
    lon_centroid = models.DecimalField(max_digits=65535,
                                       decimal_places=65535,
                                       blank=True,
                                       null=True)
    lat_centroid = models.DecimalField(max_digits=65535,
                                       decimal_places=65535,
                                       blank=True,
                                       null=True)
    county_gid = models.IntegerField(blank=True, null=True)
    cd_gid = models.IntegerField(blank=True, null=True)
    ct_gid = models.IntegerField(blank=True, null=True)

    class Meta:
        managed = False
        db_table = 'parcel'

    def __str__(self):
        return '%s' % self.pin
예제 #6
0
class DataLoader(models.Model):
    """
    """
    REPLACE_DATA_CODE = 1
    UPDATE_DATA_CODE = 2

    DATA_LOADER_MODE_CHOICES = (
        (REPLACE_DATA_CODE, 'Replace/Insert Data'),
        (UPDATE_DATA_CODE, 'Update Data')
    )

    COMMA_CHAR = ','
    TAB_CHAR = '\t'

    COMMA_CODE = 1  # ,
    TAB_CODE = 2  # \t

    SEPARATORS = {
        COMMA_CODE: COMMA_CHAR,
        TAB_CODE: TAB_CHAR
    }

    SEPARATOR_CHOICES = (
        (COMMA_CODE, 'Comma'),
        (TAB_CODE, 'Tab'),
    )

    organisation_name = models.CharField(
        verbose_name='Organisation\'s Name',
        help_text='Organiation\'s Name',
        null=False,
        blank=False,
        max_length=100
    )

    json_concept_mapping = models.FileField(
        verbose_name='JSON Concept Mapping',
        help_text='JSON Concept Mapping File.',
        upload_to='json_mapping/%Y/%m/%d',
        max_length=100
    )

    csv_data = models.FileField(
        verbose_name='CSV Data',
        help_text='CSV data that contains the data.',
        upload_to='csv_data/%Y/%m/%d',
        max_length=100
    )

    data_loader_mode = models.IntegerField(
        choices=DATA_LOADER_MODE_CHOICES,
        verbose_name='Data Loader Mode',
        help_text='The mode of the data loader.',
        blank=False,
        null=False
    )

    applied = models.BooleanField(
        verbose_name='Applied',
        help_text='Whether the data update has been applied or not.',
        default=False
    )

    author = models.ForeignKey(
        User,
        verbose_name='Author',
        help_text='The user who propose the data loader.',
        null=False
    )

    date_time_uploaded = models.DateTimeField(
        verbose_name='Uploaded (time)',
        help_text='Timestamp (UTC) when the data uploaded',
        null=False,
    )

    date_time_applied = models.DateTimeField(
        verbose_name='Applied (time)',
        help_text='When the data applied (loaded)',
        null=True
    )

    separator = models.IntegerField(
        choices=SEPARATOR_CHOICES,
        verbose_name='Separator Character',
        help_text='Separator character.',
        null=False,
        default=COMMA_CODE
    )

    notes = models.TextField(
        verbose_name='Notes',
        help_text='Notes',
        null=True,
        blank=True,
        default=''
    )

    def __str__(self):
        return self.organisation_name

    def __unicode__(self):
        return u'%s' % (self.organisation_name)

    def save(self, *args, **kwargs):
        if not self.date_time_uploaded:
            self.date_time_uploaded = datetime.utcnow()
        super(DataLoader, self).save(*args, **kwargs)
예제 #7
0
파일: instance.py 프로젝트: babacar/onadata
class InstanceHistory(models.Model, InstanceBaseClass):

    class Meta:
        app_label = 'logger'

    xform_instance = models.ForeignKey(
        Instance, related_name='submission_history', on_delete=models.CASCADE)
    user = models.ForeignKey(User, null=True, on_delete=models.CASCADE)

    xml = models.TextField()
    # old instance id
    uuid = models.CharField(max_length=249, default=u'')

    date_created = models.DateTimeField(auto_now_add=True)
    date_modified = models.DateTimeField(auto_now=True)
    submission_date = models.DateTimeField(null=True, default=None)
    geom = models.GeometryCollectionField(null=True)
    checksum = models.CharField(max_length=64, null=True, blank=True)

    @property
    def xform(self):
        return self.xform_instance.xform

    @property
    def attachments(self):
        return self.xform_instance.attachments.all()

    @property
    def json(self):
        return self.get_full_dict(load_existing=False)

    @property
    def status(self):
        return self.xform_instance.status

    @property
    def tags(self):
        return self.xform_instance.tags

    @property
    def notes(self):
        return self.xform_instance.notes.all()

    @property
    def reviews(self):
        return self.xform_instance.reviews.all()

    @property
    def version(self):
        return self.xform_instance.version

    @property
    def osm_data(self):
        return self.xform_instance.osm_data

    @property
    def deleted_at(self):
        return None

    @property
    def total_media(self):
        return self.xform_instance.total_media

    @property
    def has_a_review(self):
        return self.xform_instance.has_a_review

    @property
    def media_count(self):
        return self.xform_instance.media_count

    @property
    def media_all_received(self):
        return self.xform_instance.media_all_received

    def _set_parser(self):
        if not hasattr(self, "_parser"):
            self._parser = XFormInstanceParser(
                self.xml, self.xform_instance.xform
            )

    @classmethod
    def set_deleted_at(cls, instance_id, deleted_at=timezone.now()):
        return None
예제 #8
0
class EditLog(models.Model):
    editlogid = models.UUIDField(primary_key=True, default=uuid.uuid1)
    resourcedisplayname = models.TextField(blank=True, null=True)
    resourceclassid = models.TextField(blank=True, null=True)
    resourceinstanceid = models.TextField(blank=True, null=True)
    nodegroupid = models.TextField(blank=True, null=True)
    tileinstanceid = models.TextField(blank=True, null=True)
    edittype = models.TextField(blank=True, null=True)
    newvalue = JSONField(blank=True, null=True, db_column='newvalue')
    oldvalue = JSONField(blank=True, null=True, db_column='oldvalue')
    newprovisionalvalue = JSONField(blank=True,
                                    null=True,
                                    db_column='newprovisionalvalue')
    oldprovisionalvalue = JSONField(blank=True,
                                    null=True,
                                    db_column='oldprovisionalvalue')
    timestamp = models.DateTimeField(blank=True, null=True)
    userid = models.TextField(blank=True, null=True)
    user_firstname = models.TextField(blank=True, null=True)
    user_lastname = models.TextField(blank=True, null=True)
    user_email = models.TextField(blank=True, null=True)
    user_username = models.TextField(blank=True, null=True)
    note = models.TextField(blank=True, null=True)

    class Meta:
        managed = True
        db_table = 'edit_log'
예제 #9
0
class Node(models.Model):
    """
    Name is unique across all resources because it ties a node to values within tiles. Recommend prepending resource class to node name.

    """

    nodeid = models.UUIDField(primary_key=True, default=uuid.uuid1)
    name = models.TextField()
    description = models.TextField(blank=True, null=True)
    istopnode = models.BooleanField()
    ontologyclass = models.TextField(blank=True, null=True)
    datatype = models.TextField()
    nodegroup = models.ForeignKey(NodeGroup,
                                  db_column='nodegroupid',
                                  blank=True,
                                  null=True)
    graph = models.ForeignKey(GraphModel,
                              db_column='graphid',
                              blank=True,
                              null=True)
    config = JSONField(blank=True, null=True, db_column='config')
    issearchable = models.BooleanField(default=True)
    isrequired = models.BooleanField(default=False)

    def get_child_nodes_and_edges(self):
        """
        gather up the child nodes and edges of this node

        returns a tuple of nodes and edges

        """
        nodes = []
        edges = []
        for edge in Edge.objects.filter(domainnode=self):
            nodes.append(edge.rangenode)
            edges.append(edge)

            child_nodes, child_edges = edge.rangenode.get_child_nodes_and_edges(
            )
            nodes.extend(child_nodes)
            edges.extend(child_edges)
        return (nodes, edges)

    @property
    def is_collector(self):
        return str(self.nodeid) == str(
            self.nodegroup_id) and self.nodegroup is not None

    def get_relatable_resources(self):
        relatable_resource_ids = [
            r2r.resourceclassfrom
            for r2r in Resource2ResourceConstraint.objects.filter(
                resourceclassto_id=self.nodeid)
        ]
        relatable_resource_ids = relatable_resource_ids + [
            r2r.resourceclassto
            for r2r in Resource2ResourceConstraint.objects.filter(
                resourceclassfrom_id=self.nodeid)
        ]
        return relatable_resource_ids

    def set_relatable_resources(self, new_ids):
        old_ids = [res.nodeid for res in self.get_relatable_resources()]
        for old_id in old_ids:
            if old_id not in new_ids:
                Resource2ResourceConstraint.objects.filter(
                    Q(resourceclassto_id=self.nodeid)
                    | Q(resourceclassfrom_id=self.nodeid),
                    Q(resourceclassto_id=old_id)
                    | Q(resourceclassfrom_id=old_id)).delete()
        for new_id in new_ids:
            if new_id not in old_ids:
                new_r2r = Resource2ResourceConstraint.objects.create(
                    resourceclassfrom_id=self.nodeid,
                    resourceclassto_id=new_id)
                new_r2r.save()

    class Meta:
        managed = True
        db_table = 'nodes'
예제 #10
0
class GeographicRegion(TranslatableModel, models.Model, ContentFetchingMixin):
    """Common model to represent levels 1, 2, 3"""
    __translatable__ = {
        "title": lambda l: models.CharField(
            _("Title in {LANGUAGE_NAME}".format(**l)),
            max_length=256,
            default='',
            blank=True,
        ),
    }

    level = models.IntegerField(
        choices=[
            (1, _('Country')),
            (2, _('Region')),
            (3, _('City')),
        ]
    )
    geom = models.MultiPolygonField(srid=4326,blank=True,
        null=True,)
    parent = models.ForeignKey('self', related_name='children', null=True, blank=True)
    name = models.CharField(max_length=256, default='')

    slug = models.CharField(max_length=100, default='')
    code = models.CharField(max_length=16, blank=True)

    hidden = models.BooleanField(default=False)

    languages_available = models.CharField(
        max_length=300,
        blank=True,
        help_text=_('Comma separated values of languages available in this region')
    )
    restrict_access_to = models.TextField(
        null=True,
        blank=True,
        help_text=_('Comma separated values of code of siblings visible from this region')
    )

    site = models.ForeignKey(Site, related_name='+', null=True, blank=True)
    
    objects = models.GeoManager()

    def __str__(self):
        return "%s %s" % (self.get_level_display(), self.name)

    @property
    def centroid(self):
        return self.geom.centroid 

    @property
    def depth(self):
        return len(list(self.parents))

    @property
    def parents(self):
        me = self
        while me.parent:
            me = me.parent

            yield me

    @property
    def important_information(self):
        pages = [{
                     "id": p.page.id,
                     "slug": p.page.slug,
                     "code": p.page.slug,
                     "title": p.page.title,
                     "name": p.page.title,
                     "hidden": False,
                     "metadata": {"page_title": p.page.title,},
                     "content": [{
                         "vector_icon": p.page.icon,
                         "hide_from_toc": p.page.pop_up,
                         "section": p.page.html(),
                         "metadata": {},
                         "title": p.page.title,
                         "important": False,
                         "anchor_name": p.page.slug,
                         "index": i,
                         "inherited": False,
                     }] + [{
                               "vector_icon": "",
                               "hide_from_toc": True,
                               "section": sp['html'],
                               "metadata": {
                                   "page_title": sp['title']
                               },
                               "title": sp['title'],
                               "important": False,
                               "anchor_name": sp['slug'],
                               "index": z,
                               "inherited": False,
                           } for z, sp in enumerate(p.page.get_sub_sections())
                           ]
                 } for i, p in enumerate(self.pages_with_order.filter(page__important=True).order_by('index'))]
        return pages

    @property
    def full_slug(self):
        return "--".join(reversed([self.slug] + [p.slug for p in self.parents]))

    @property
    def uri(self):
        return "/".join(reversed([self.slug] + [p.slug for p in self.parents if p.level != 2])) + '/'

    def get_all_languages(self):
        return set([])

    def get_sections(self, language='en', environment='production'):
        pages = [{
                     "vector_icon": p.page.icon,
                     "hide_from_toc": p.page.pop_up,
                     "section": p.page.html(language),
                     "metadata": {
                         "page_title": p.page.title
                     },
                     "title": p.page.title,
                     "important": False,
                     "anchor_name": p.page.slug,
                     "index": i,
                     "inherited": False,
                 } for i, p in enumerate(
            self.pages_with_order.filter(page__important=False, page__banner=False, page__status=environment).order_by(
                'index'))]

        page_like_objects = [
            [
                {
                    "vector_icon": "",
                    "hide_from_toc": True,
                    "section": sp['html'],
                    "metadata": {
                        "page_title": sp['title']
                    },
                    "title": sp['title'],
                    "important": False,
                    "anchor_name": sp['slug'],
                    "index": i,
                    "inherited": False,
                }
                for i, sp in enumerate(p.page.get_sub_sections(language))
                ]
            for p in self.pages_with_order.filter(page__important=False, page__banner=False, page__status=environment)
            ]
        return pages + list(itertools.chain.from_iterable(page_like_objects))

    def get_sub_pages(self, environment='production'):
        pages = [{
                     "id": p.page.id,
                     "slug": p.page.slug,
                     "code": p.page.slug,
                     "title": p.page.title,
                     "name": p.page.title,
                     "hidden": False,
                     "metadata": {"page_title": p.page.title,},
                     "content": [{
                         "vector_icon": p.page.icon,
                         "hide_from_toc": p.page.pop_up,
                         "section": p.page.html(),
                         "metadata": {},
                         "title": p.page.title,
                         "important": False,
                         "anchor_name": p.page.slug,
                         "index": i,
                         "inherited": False,
                     }] + [{
                               "vector_icon": "",
                               "hide_from_toc": True,
                               "section": sp['html'],
                               "metadata": {
                                   "page_title": sp['title']
                               },
                               "title": sp['title'],
                               "important": False,
                               "anchor_name": sp['slug'],
                               "index": z,
                               "inherited": False,
                           } for z, sp in enumerate(p.page.get_sub_sections())
                           ]
                 } for i, p in enumerate(
            self.pages_with_order.filter(page__important=True, page__banner=False, page__status=environment).order_by(
                'index'))]
        return pages

    def metadata(self, language='en', environment='production'):
        banners = self.pages_with_order.filter(page__banner=True, page__status=environment)
        return {
            "banners": [p.page.html() for p in banners],
            "page_title": self.title
        }

    def get_all_children(self):
        return GeographicRegion.objects.filter(Q(parent=self) | Q(parent__parent=self) | Q(id=self.id))

    class Meta:
        ordering = ['level', 'name']
예제 #11
0
class EventAgendaItem(RelatedBase):
    description = models.TextField()
    order = models.CharField(max_length=100, blank=True)
    subjects = ArrayField(dbtype='text')
    notes = ArrayField(dbtype='text')
    event = models.ForeignKey(Event, related_name='agenda')
예제 #12
0
class HDXExportRegion(models.Model, RegionMixin):  # noqa
    """ Mutable database table for hdx - additional attributes on a Job."""
    schedule_period = models.CharField(blank=False,
                                       max_length=10,
                                       default="disabled",
                                       choices=PERIOD_CHOICES)
    schedule_hour = models.IntegerField(blank=False,
                                        choices=HOUR_CHOICES,
                                        default=0)
    deleted = models.BooleanField(default=False)
    # a job should really be required, but that interferes with DRF validation lifecycle.
    job = models.ForeignKey(Job,
                            null=True,
                            related_name='hdx_export_region_set')
    is_private = models.BooleanField(default=False)
    locations = ArrayField(models.CharField(blank=False, max_length=32),
                           null=True)
    license = models.CharField(max_length=32, null=True, blank=True)
    subnational = models.BooleanField(default=True)
    extra_notes = models.TextField(null=True, blank=True)
    planet_file = models.BooleanField(default=False)

    class Meta:  # noqa
        db_table = 'hdx_export_regions'

    def __str__(self):
        return self.name + " (prefix: " + self.dataset_prefix + ")"

    def clean(self):
        if self.job and not re.match(r'^[a-z0-9-_]+$', self.job.name):
            raise ValidationError({
                'dataset_prefix':
                "Invalid dataset_prefix: {0}".format(self.job.name)
            })

    @property
    def buffer_aoi(self):  # noqa
        return self.job.buffer_aoi

    @property
    def name(self):  # noqa
        return self.job.description

    @property
    def dataset_prefix(self):  # noqa
        return self.job.name

    @property
    def datasets(self):  # noqa
        export_set = HDXExportSet(Mapping(self.feature_selection),
                                  self.dataset_prefix, self.name,
                                  self.extra_notes)
        return export_set.dataset_links(settings.HDX_URL_PREFIX)

    @property
    def update_frequency(self):
        """Update frequencies in HDX form."""
        if self.schedule_period == '6hrs':
            return 1

        if self.schedule_period == 'daily':
            return 1

        if self.schedule_period == 'weekly':
            return 7

        if self.schedule_period == 'monthly':
            return 30

        return 0
예제 #13
0
class Job(models.Model):
    """ 
    Database model for an 'Export'.
    Immutable, except in the case of HDX Export Regions.
    """
    id = models.AutoField(primary_key=True, editable=False)
    uid = models.UUIDField(unique=True,
                           default=uuid.uuid4,
                           editable=False,
                           db_index=True)
    user = models.ForeignKey(User, related_name='owner')
    name = models.CharField(max_length=100, db_index=True, blank=False)
    description = models.CharField(max_length=1000,
                                   db_index=True,
                                   default='',
                                   blank=True)
    event = models.CharField(max_length=100,
                             db_index=True,
                             default='',
                             blank=True)
    export_formats = ArrayField(models.CharField(max_length=10),
                                validators=[validate_export_formats],
                                blank=False)
    published = models.BooleanField(default=False, db_index=True)
    the_geom = models.GeometryField(verbose_name='Uploaded geometry',
                                    srid=4326,
                                    blank=False)
    simplified_geom = models.GeometryField(verbose_name='Simplified geometry',
                                           srid=4326,
                                           blank=True,
                                           null=True)
    objects = models.GeoManager()
    feature_selection = models.TextField(
        blank=False, validators=[validate_feature_selection])
    created_at = models.DateTimeField(default=timezone.now, editable=False)
    updated_at = models.DateTimeField(default=timezone.now, editable=False)
    mbtiles_maxzoom = models.IntegerField(null=True, blank=True)
    mbtiles_minzoom = models.IntegerField(null=True, blank=True)
    mbtiles_source = models.TextField(null=True, blank=True)

    # flags
    buffer_aoi = models.BooleanField(default=False)
    unlimited_extent = models.BooleanField(default=False)
    hidden = models.BooleanField(default=False)  # hidden from the list page
    expire_old_runs = models.BooleanField(default=True)
    per_theme = models.BooleanField(default=False)

    class Meta:  # pragma: no cover
        managed = True
        db_table = 'jobs'

    @property
    def osma_link(self):
        bounds = self.the_geom.extent
        return "http://osm-analytics.org/#/show/bbox:{0},{1},{2},{3}/buildings/recency".format(
            *bounds)

    @property
    def area(self):
        return get_geodesic_area(self.the_geom)

    def save(self, *args, **kwargs):
        self.the_geom = force2d(self.the_geom)
        self.simplified_geom = simplify_geom(self.the_geom,
                                             force_buffer=self.buffer_aoi)
        super(Job, self).save(*args, **kwargs)

    def __str__(self):
        return str(self.uid)
예제 #14
0
class AuthUser(AbstractBaseUser, PermissionsMixin):
    account = models.ForeignKey(Account,
                                null=True,
                                blank=True,
                                related_name='users')
    email = models.EmailField(
        _('email address'),
        unique=True,
        help_text=_('Required.'),
        error_messages={
            'unique': _('The given email address has already been registered.')
        })
    first_name = models.CharField(_('first name'), max_length=30, blank=True)
    last_name = models.CharField(_('last name'), max_length=30, blank=True)
    metadata = models.TextField(blank=True)

    is_account_admin = models.BooleanField(
        _('account admin status'),
        default=False,
        help_text=_('Designates whether the user is as account admin.'))

    is_staff = models.BooleanField(
        _('staff status'),
        default=False,
        help_text=_(
            'Designates whether the user can log into the admin site.'))
    is_active = models.BooleanField(
        _('active'),
        default=True,
        help_text=_('Designates whether this user should be treated as '
                    'active. Unselect this instead of deleting accounts.'))
    created_at = models.DateTimeField(_('created at'), editable=False)
    updated_at = models.DateTimeField(_('updated at'), editable=False)

    objects = AuthUserManager()

    USERNAME_FIELD = 'email'

    class Meta:
        verbose_name = _('user')
        verbose_name_plural = _('users')

    def get_full_name(self):
        full_name = '%s %s' % (self.first_name, self.last_name)
        return full_name.strip()

    def get_short_name(self):
        return self.first_name

    def get_token(self):
        try:
            token = Token.objects.get(user=self)
        except Token.DoesNotExist:
            token = ''
        return str(token)

    def email_user(self, subject, message, from_email=None, **kwargs):
        send_mail(subject, message, from_email, [self.email], **kwargs)

    def save(self, *args, **kwargs):
        now = datetime.now(timezone.utc)
        if not self.id:
            self.created_at = now
            self.updated_at = now
            if self.account is not None and len(
                    AuthUser.objects.filter(account=self.account,
                                            is_account_admin=True)) == 0:
                self.is_account_admin = True  # an account needs an account admin, so make it the first user to enroll
        else:
            self.updated_at = now
        super(AuthUser, self).save(*args, **kwargs)
예제 #15
0
class Vendor(models.Model):
    name = models.CharField(max_length=256, unique=True)
    details = models.TextField(blank=True)
    extra_data = JSONField(null=True)
예제 #16
0
class Occurrence(projects.models.PaleoCoreOccurrenceBaseClass):
    """
    Occurrence <- PaleoCoreOccurrenceBaseClass <- PaleoCoreGeomBaseClass <- PaleoCoreBaseClass
    """
    basis_of_record = models.CharField("Basis of Record", max_length=50, blank=True, null=False,
                                       choices=BASIS_OF_RECORD_VOCABULARY)  # NOT NULL
    item_type = models.CharField("Item Type", max_length=255, blank=True, null=False,
                                 choices=ITEM_TYPE_VOCABULARY)  # NOT NULL
    collection_code = models.CharField("Collection Code", max_length=20, blank=True, null=True, default='WT')
    # Note we're not using localities!
    item_number = models.IntegerField("Item #", null=True, blank=True)
    item_part = models.CharField("Item Part", max_length=10, null=True, blank=True)
    catalog_number = models.CharField("Catalog #", max_length=255, blank=True, null=True)
    item_scientific_name = models.CharField("Sci Name", max_length=255, null=True, blank=True)
    item_description = models.CharField("Description", max_length=255, blank=True, null=True)
    # georeference_remarks = models.CharField(max_length=50, null=True, blank=True)
    collecting_method = models.CharField("Collecting Method", max_length=50,
                                         choices=COLLECTING_METHOD_VOCABULARY, null=False)  # NOT NULL
    related_catalog_items = models.CharField("Related Catalog Items", max_length=50, null=True, blank=True)
    collector = models.CharField(max_length=50, blank=True, null=True, choices=COLLECTOR_CHOICES)
    finder = models.CharField(max_length=50, blank=True, null=True)
    disposition = models.CharField(max_length=255, blank=True, null=True)
    field_season = models.CharField(max_length=50, null=True, blank=True, choices=FIELD_SEASON_CHOICES)
    individual_count = models.IntegerField(blank=True, null=True, default=1)
    preparation_status = models.CharField(max_length=50, blank=True, null=True)
    stratigraphic_marker_upper = models.CharField(max_length=255, blank=True, null=True)
    distance_from_upper = models.DecimalField(max_digits=38, decimal_places=8, blank=True, null=True)
    stratigraphic_marker_lower = models.CharField(max_length=255, blank=True, null=True)
    distance_from_lower = models.DecimalField(max_digits=38, decimal_places=8, blank=True, null=True)
    stratigraphic_marker_found = models.CharField(max_length=255, blank=True, null=True)
    distance_from_found = models.DecimalField(max_digits=38, decimal_places=8, blank=True, null=True)
    stratigraphic_marker_likely = models.CharField(max_length=255, blank=True, null=True)
    distance_from_likely = models.DecimalField(max_digits=38, decimal_places=8, blank=True, null=True)
    stratigraphic_member = models.CharField(max_length=255, blank=True, null=True)
    analytical_unit = models.CharField("Submember", max_length=255, blank=True, null=True)
    analytical_unit_2 = models.CharField(max_length=255, blank=True, null=True)
    analytical_unit_3 = models.CharField(max_length=255, blank=True, null=True)
    in_situ = models.BooleanField(default=False)
    ranked = models.BooleanField(default=False)
    image = models.FileField(max_length=255, blank=True, upload_to="uploads/images/mlp", null=True)
    weathering = models.SmallIntegerField(blank=True, null=True)
    surface_modification = models.CharField(max_length=255, blank=True, null=True)

    # Verbatim Fields
    verbatim_kml_data = models.TextField(null=True, blank=True)

    def __str__(self):
        """
        What is the best string representation for an occurrence instance?
        All collected items have catalogue numbers, but observations do not
        This method returns the catalog number if it exists, or a string with the id value
        if there is no catalog number.
        """
        if self.catalog_number:
            return self.catalog_number
        else:
            return "item "+str(self.id)

    class Meta:
        managed = True
        verbose_name = app_name.upper()+" Occurrence"
        verbose_name_plural = app_name.upper()+" Occurrences"
예제 #17
0
class Locality(UpdateMixin, ChangesetMixin):
    """
    A Locality is uniquely defined by an *uuid* attribute. Attribute *geom*
    stores geometry as a point object. *upstream_id* is used to preserve link
    to the originating dataset which is used to find and update a Locality on
    any reoccurring data imports.

    A Locality is in a *Domain* and data values for Attributes, to be exact,
    their Specifications, are defined through *Value*
    """
    DEFINED_DAYS = ['mon', 'tue', 'wed', 'thu', 'fri', 'sat', 'sun']

    domain = models.ForeignKey('Domain')
    uuid = models.TextField(unique=True)
    upstream_id = models.TextField(null=True, unique=True)
    geom = models.PointField(srid=4326)
    specifications = models.ManyToManyField('Specification', through='Value')
    name = models.TextField()
    source = models.TextField(default='healthsites.io')

    # completeness is a big calculation
    # so it has to be an field
    completeness = models.FloatField(null=True, default=0.0)
    is_master = models.BooleanField(default=True)

    objects = PassThroughGeoManager.for_queryset_class(LocalitiesQuerySet)()
    tracker = FieldTracker()

    def before_save(self, *args, **kwargs):
        # make sure that we don't allow uuid modifications
        if self.tracker.previous('uuid') and self.tracker.has_changed('uuid'):
            self.uuid = self.tracker.previous('uuid')

    def _get_attr_map(self):
        return (
            self.domain.specification_set
                .order_by('id')
                .values('id', 'attribute__key')
        )

    def set_geom(self, lon, lat):
        """
        Helper method to set Locality geometry
        """

        self.geom.set_x(lon)
        self.geom.set_y(lat)

    def set_values(self, changed_data, social_user, changeset=None):
        """
        Set values for a Locality which are defined by Specifications

        Once all of values are set, 'SIG_locality_values_updated' signal will
        be triggered to update FullTextSearch index for this Locality
        """
        special_key = [
            'scope_of_service', 'ancillary_services', 'activities', 'inpatient_service', 'staff'
        ]
        attrs = self._get_attr_map()

        tmp_changeset = changeset

        changed_values = []
        for key, data in changed_data.iteritems():
            if key in special_key:
                data = data.replace(',', '|')
                data = data.replace('| ', '|')
            # try to match key from changed items with a key from attr_map
            attr_list = [
                attr for attr in attrs if attr['attribute__key'] == key
            ]

            if attr_list:
                # get specification id for specific key
                spec_id = attr_list[0]['id']

                # update or create new values
                try:
                    obj = self.value_set.get(specification_id=spec_id)
                    _created = False
                except Value.DoesNotExist:
                    # in case there is no value for the specification, create
                    obj = Value()
                    obj.locality = self
                    obj.specification_id = spec_id
                    _created = True

                # set data
                obj.data = data

                # check if Value.data actually changed, and save if it did
                if obj.tracker.changed():
                    if not (tmp_changeset):
                        tmp_changeset = Changeset.objects.create(
                            social_user=social_user
                        )
                    obj.changeset = tmp_changeset
                    obj.save()
                    changed_values.append((obj, _created))
                else:
                    # nothing changed, don't save the value
                    pass
            else:
                # attr_id was not found (maybe a bad attribute)
                LOG.warning(
                    'Locality %s has no attribute key %s', self.pk, key
                )

        # send values_updated signal
        signals.SIG_locality_values_updated.send(
            sender=self.__class__, instance=self
        )

        # calculate completeness
        if changed_values:
            self.completeness = self.calculate_completeness()
            self.save()

        return changed_values

    def repr_dict(self, clean=False, in_array=False):
        """
        Basic locality representation, as a dictionary
        """

        dict = {
            u'uuid': self.uuid,
            u'upstream': self.upstream_id,
            u'source': self.source,
            u'name': self.name,
            u'geom': (self.geom.x, self.geom.y),
            u'version': self.version,
            u'date_modified': self.changeset.created,
            u'completeness': '%s%%' % format(self.completeness, '.2f'),
        }

        dict['values'] = {}

        data_query = (
            self.value_set.select_related().exclude(data__isnull=True).exclude(data__exact='')
        )

        for val in data_query:
            if in_array:
                dict['values'][val.specification.attribute.key] = [
                    data for data in val.data.split('|') if data]

                clean_data = dict['values'][val.specification.attribute.key]
                if len(clean_data) == 0:
                    dict['values'][val.specification.attribute.key] = '-'
                elif len(clean_data) == 1:
                    dict['values'][val.specification.attribute.key] = clean_data[0]
                continue
            if clean:
                # clean if empty
                temp = val.data.replace('|', '')
                if len(temp) == 0:
                    val.data = ''
                # clean data
                val.data = val.data.replace('|', ',')
                val.specification.attribute.key = (
                    val.specification.attribute.key.replace('_', '-')
                )
                cleaned_data = val.data.replace(',', '')
                if len(cleaned_data) > 0:
                    dict['values'][val.specification.attribute.key] = val.data
            else:
                dict['values'][val.specification.attribute.key] = val.data

        try:
            site = Site.objects.get(name=dict[u'source'])
            dict[u'source_url'] = site.domain
        except Site.DoesNotExist:
            pass

        # exclusive for open street map
        if self.upstream_id is not None and 'openstreetmap' in self.upstream_id.lower():
            osm_whole_id = self.upstream_id.split(u'¶')
            if len(osm_whole_id) > 0:
                osm_whole_id = osm_whole_id[1]
                identifier = osm_whole_id[0]
                osm_id = osm_whole_id[1:]
                if identifier == 'n':
                    url = 'http://www.openstreetmap.org/node/' + osm_id
                elif identifier == 'r':
                    url = 'http://www.openstreetmap.org/relation/' + osm_id
                elif identifier == 'w':
                    url = 'http://www.openstreetmap.org/way/' + osm_id

                if url:
                    dict['source_url'] = url
        return dict

    def is_type(self, value):
        if value != '':
            try:
                self.value_set.filter(specification__attribute__key='type').get(data=value)
                return True
            except Exception:
                return False
        return True

    def calculate_completeness(self):
        DEFAULT_VALUE = 4  # GUID & GEOM & NAME & DATA SOURCE
        global_attr = attributes_availables['global']
        specific_attr = attributes_availables['hospital']
        for key in attributes_availables.keys():
            try:
                self.value_set.filter(specification__attribute__key='type').get(
                    data__icontains=key
                )
                specific_attr = attributes_availables[key]
            except Value.DoesNotExist:
                continue

        values = self.repr_dict()['values']
        counted_value = DEFAULT_VALUE
        max_value = len(global_attr) + len(specific_attr) + DEFAULT_VALUE

        for attr in global_attr + specific_attr:
            if attr in values:
                data = values[attr]
                if len(data.replace('-', '').replace('|', '').strip()) != 0:
                    counted_value += 1

        return (counted_value + 0.0) / (max_value + 0.0) * 100

    def prepare_for_fts(self):
        """
        Retrieve and group *Value* objects, for this Locality, based on their
        FTS ordering (defined by *Specification*)
        """

        data_values = itertools.groupby(
            self.value_set.order_by('specification__fts_rank')
                .values_list('specification__fts_rank', 'data'),
            lambda x: x[0]
        )

        return {k: ' '.join([x[1] for x in v]) for k, v in data_values}

    def update_what3words(self, user, changeset):
        from utils import get_what_3_words
        what3words = get_what_3_words(self.geom)
        if what3words != '':
            self.set_values({'what3words': what3words}, user, changeset)

    def get_synonyms(self):
        synonyms = SynonymLocalities.objects.get(locality=self)
        return synonyms

    def __unicode__(self):
        return u'{}'.format(self.id)

    def validate_data_by_defined_list(self, data, key, options, required=False):
        """ Check value data by key if it is string and it is in options.

        :param data: Data to be inserted
        :param key: Key data that is checked
        :param options: Options to be checked
        :return:
        """
        try:
            value = data[key]
            if isinstance(value, list):
                raise ValueError(
                    'nature_of_facility should be string'
                )
            if value not in options:
                raise ValueError(
                    '%s is not recognized, options : %s' % (key, options)
                )

        except KeyError as e:
            if required:
                raise ValueError('%s is required' % e)
            pass

    def validate_data(self, data):
        """ Validate data based on fields

        :param data: Data that will be inserted
        :type data: dict
        """
        try:
            data['lng'] = float(data['lng'])
        except ValueError:
            raise ValueError('lng is not in float')
        try:
            data['lat'] = float(data['lat'])
        except ValueError:
            raise ValueError('lat is not in float')

        if not data['name']:
            raise ValueError('name is empty')

        domain = Domain.objects.get(name='Health')
        attributes = Specification.objects.filter(domain=domain).filter(required=True)
        for attribute in attributes:
            if not data[attribute.attribute.key]:
                raise ValueError('%s is empty' % attribute.attribute.key)

        # inpatient_service
        try:
            inpatient_service = data['inpatient_service']
            try:
                full_time_beds = inpatient_service['full_time_beds']
            except KeyError:
                raise ValueError(
                    'full_time_beds needs to be in inpatient_service'
                )
            try:
                part_time_beds = inpatient_service['part_time_beds']
            except KeyError:
                raise ValueError(
                    'part_time_beds needs to be in inpatient_service'
                )
            data['inpatient_service'] = '%s|%s' % (
                full_time_beds,
                part_time_beds
            )
        except KeyError:
            pass

        # staff
        try:
            staff = data['staff']
            try:
                doctors = staff['doctors']
            except KeyError:
                raise ValueError(
                    'doctors needs to be in staff'
                )
            try:
                nurses = staff['nurses']
            except KeyError:
                raise ValueError(
                    'nurses needs to be in staff'
                )
            data['staff'] = '%s|%s' % (
                doctors,
                nurses
            )
        except KeyError:
            pass

        # nature of facility
        options = ['clinic without beds',
                   'clinic with beds',
                   'first referral hospital',
                   'second referral hospital or General hospital',
                   'tertiary level including University hospital']
        self.validate_data_by_defined_list(
            data, 'nature_of_facility', options)

        # nature of facility
        options = ['public',
                   'private not for profit',
                   'private commercial']
        self.validate_data_by_defined_list(
            data, 'ownership', options)

        # defined_hours
        try:
            defined_hours = []
            for index, day in enumerate(Locality.DEFINED_DAYS):
                try:
                    hours = data['defining_hours'][day]
                    if isinstance(hours, str) or isinstance(hours, unicode):
                        if hours == '':
                            hours = []
                        else:
                            try:
                                hours = json.loads(hours)
                            except ValueError:
                                pass
                    if not isinstance(hours, list):
                        raise ValueError('%s is need to be in list' % day)
                    if len(hours) == 1:
                        hours.append('-')
                    elif len(hours) > 2:
                        raise ValueError('maximum lenght of %s is 2' % day)
                    hours = '-'.join(hours)
                    if not hours:
                        hours = '-'
                    defined_hours.append(hours)
                except KeyError as e:
                    raise ValueError('%s is required on defined_hours' % e)
            data['defining_hours'] = defined_hours
        except KeyError:
            pass
        except TypeError:
            raise TypeError('defining_hours needs to be in dictionary')

        return True

    def update_data(self, data, user):
        """ Update locality data with new data.

        :param data: Data that will be inserted
        :type data: dict
        """
        import uuid
        from django.contrib.gis.geos import Point
        from localities.tasks import regenerate_cache, regenerate_cache_cluster

        self.validate_data(data)
        old_geom = None
        try:
            old_geom = [self.geom.x, self.geom.y]
            self.set_geom(data['lng'], data['lat'])
        except AttributeError:
            self.geom = Point(data['lng'], data['lat'])

        self.name = data['name']

        # there are some changes so create a new changeset
        changeset = Changeset.objects.create(
            social_user=user
        )
        self.changeset = changeset

        del data['lng']
        del data['lat']

        created = False
        if not self.pk:
            created = True
            self.domain = Domain.objects.get(name='Health')
            self.changeset = changeset
            self.uuid = uuid.uuid4().hex
            self.upstream_id = u'web¶{}'.format(self.uuid)
            self.save()

        self.set_specifications(data, changeset)
        if not created and self.tracker.changed():
            self.changeset = changeset
            self.save()

        # generate some attributes if location changed/created
        new_geom = [self.geom.x, self.geom.y]
        if new_geom != old_geom or created:
            try:
                self.update_what3words(user, changeset)
            except AttributeError:
                pass
            regenerate_cache_cluster.delay()
        regenerate_cache.delay(changeset.pk, self.pk)
        return True

    def set_specifications(
            self, data, changeset, autocreate_specification=True):
        """
        Set values for a Locality which are defined by Specifications

        Once all of values are set, 'SIG_locality_values_updated' signal will
        be triggered to update FullTextSearch index for this Locality

        :param data: Data to be inserted as specification
        :type data: dict
        """
        fields = self._meta.get_all_field_names()
        domain = Domain.objects.get(name='Health')

        changed_values = []
        for key, value in data.iteritems():
            if key in fields:
                continue

            if isinstance(value, list):
                value = '|'.join(value)
            else:
                value = '%s' % value

            value = value.replace(',', '|')
            value = value.replace('| ', '|')

            try:
                specification = Specification.objects.get(
                    domain=domain, attribute__key=key)
            except Specification.DoesNotExist:
                if autocreate_specification:
                    try:
                        attribute = Attribute.objects.get(key=key)
                    except Attribute.DoesNotExist:
                        attribute = Attribute.objects.create(
                            key=key, changeset=changeset)
                    specification = Specification.objects.create(
                        domain=domain, attribute=attribute, changeset=changeset)
                else:
                    continue

            try:
                obj = self.value_set.get(specification=specification)
            except Value.DoesNotExist:
                # in case there is no value for the specification, create
                obj = Value()
                obj.locality = self
                obj.specification = specification

            obj.data = value

            # check if Value.data actually changed, and save if it did
            if obj.tracker.changed():
                obj.changeset = changeset
                obj.save()
                changed_values.append(obj)
            else:
                # nothing changed, don't save the value
                pass

        # send values_updated signal
        signals.SIG_locality_values_updated.send(
            sender=self.__class__, instance=self
        )

        # calculate completeness
        if changed_values:
            self.completeness = self.calculate_completeness()
            self.save()

        return changed_values
예제 #18
0
class Fossil(Occurrence):
    """
    Biology <- Occurrence <- PaleoCoreOccurrenceBaseClass <- PaleoCoreGeomBaseClass <- PaleoCoreBaseClass
    """
    infraspecific_epithet = models.CharField(null=True, blank=True, max_length=50)
    infraspecific_rank = models.CharField(null=True, blank=True, max_length=50)
    author_year_of_scientific_name = models.CharField(null=True, blank=True, max_length=50)
    nomenclatural_code = models.CharField(null=True, blank=True, max_length=50)
    identified_by = models.CharField(null=True, blank=True, max_length=100, choices=COLLECTOR_CHOICES)
    date_identified = models.DateTimeField(null=True, blank=True)
    identification_remarks = models.TextField(null=True, blank=True, max_length=64000)
    type_status = models.CharField(null=True, blank=True, max_length=50)
    sex = models.CharField(null=True, blank=True, max_length=50)
    life_stage = models.CharField(null=True, blank=True, max_length=50)
    preparations = models.CharField(null=True, blank=True, max_length=50)
    morphobank_number = models.IntegerField(null=True, blank=True)
    side = models.CharField(null=True, blank=True, max_length=50, choices=SIDE_VOCABULARY)
    attributes = models.CharField(null=True, blank=True, max_length=50)
    tooth_upper_or_lower = models.CharField(null=True, blank=True, max_length=10)
    tooth_number = models.CharField(null=True, blank=True, max_length=50)
    tooth_type = models.CharField(null=True, blank=True, max_length=50)
    um_tooth_row_length_mm = models.DecimalField(max_digits=38, decimal_places=8, null=True, blank=True)
    um_1_length_mm = models.DecimalField(max_digits=38, decimal_places=8, null=True, blank=True)
    um_1_width_mm = models.DecimalField(max_digits=38, decimal_places=8, null=True, blank=True)
    um_2_length_mm = models.DecimalField(max_digits=38, decimal_places=8, null=True, blank=True)
    um_2_width_mm = models.DecimalField(max_digits=38, decimal_places=8, null=True, blank=True)
    um_3_length_mm = models.DecimalField(max_digits=38, decimal_places=8, null=True, blank=True)
    um_3_width_mm = models.DecimalField(max_digits=38, decimal_places=8, null=True, blank=True)
    lm_tooth_row_length_mm = models.DecimalField(max_digits=38, decimal_places=8, null=True, blank=True)
    lm_1_length = models.DecimalField(max_digits=38, decimal_places=8, null=True, blank=True)
    lm_1_width = models.DecimalField(max_digits=38, decimal_places=8, null=True, blank=True)
    lm_2_length = models.DecimalField(max_digits=38, decimal_places=8, null=True, blank=True)
    lm_2_width = models.DecimalField(max_digits=38, decimal_places=8, null=True, blank=True)
    lm_3_length = models.DecimalField(max_digits=38, decimal_places=8, null=True, blank=True)
    lm_3_width = models.DecimalField(max_digits=38, decimal_places=8, null=True, blank=True)
    element = models.CharField(null=True, blank=True, max_length=50)
    element_modifier = models.CharField(null=True, blank=True, max_length=50)
    uli1 = models.BooleanField(default=False)
    uli2 = models.BooleanField(default=False)
    uli3 = models.BooleanField(default=False)
    uli4 = models.BooleanField(default=False)
    uli5 = models.BooleanField(default=False)
    uri1 = models.BooleanField(default=False)
    uri2 = models.BooleanField(default=False)
    uri3 = models.BooleanField(default=False)
    uri4 = models.BooleanField(default=False)
    uri5 = models.BooleanField(default=False)
    ulc = models.BooleanField(default=False)
    urc = models.BooleanField(default=False)
    ulp1 = models.BooleanField(default=False)
    ulp2 = models.BooleanField(default=False)
    ulp3 = models.BooleanField(default=False)
    ulp4 = models.BooleanField(default=False)
    urp1 = models.BooleanField(default=False)
    urp2 = models.BooleanField(default=False)
    urp3 = models.BooleanField(default=False)
    urp4 = models.BooleanField(default=False)
    ulm1 = models.BooleanField(default=False)
    ulm2 = models.BooleanField(default=False)
    ulm3 = models.BooleanField(default=False)
    urm1 = models.BooleanField(default=False)
    urm2 = models.BooleanField(default=False)
    urm3 = models.BooleanField(default=False)
    lli1 = models.BooleanField(default=False)
    lli2 = models.BooleanField(default=False)
    lli3 = models.BooleanField(default=False)
    lli4 = models.BooleanField(default=False)
    lli5 = models.BooleanField(default=False)
    lri1 = models.BooleanField(default=False)
    lri2 = models.BooleanField(default=False)
    lri3 = models.BooleanField(default=False)
    lri4 = models.BooleanField(default=False)
    lri5 = models.BooleanField(default=False)
    llc = models.BooleanField(default=False)
    lrc = models.BooleanField(default=False)
    llp1 = models.BooleanField(default=False)
    llp2 = models.BooleanField(default=False)
    llp3 = models.BooleanField(default=False)
    llp4 = models.BooleanField(default=False)
    lrp1 = models.BooleanField(default=False)
    lrp2 = models.BooleanField(default=False)
    lrp3 = models.BooleanField(default=False)
    lrp4 = models.BooleanField(default=False)
    llm1 = models.BooleanField(default=False)
    llm2 = models.BooleanField(default=False)
    llm3 = models.BooleanField(default=False)
    lrm1 = models.BooleanField(default=False)
    lrm2 = models.BooleanField(default=False)
    lrm3 = models.BooleanField(default=False)
    taxon = models.ForeignKey('Taxon',
                              default=0, on_delete=models.SET_DEFAULT,  # prevent deletion when taxa deleted
                              related_name='mlp_biology_occurrences')
    identification_qualifier = models.ForeignKey('IdentificationQualifier', null=True, blank=True,
                                                 on_delete=models.SET_NULL,
                                                 related_name='mlp_biology_occurrences')

    def __str__(self):
        return str(self.taxon.__str__())

    def match_taxon(self):
        """
        find taxon objects from item_scientific_name
        Return: (True/False, match_count, match_list)
        """
        match_list = Taxon.objects.filter(name=self.item_scientific_name)
        if len(match_list) == 1:  # one match
            result_tuple = (True, 1, match_list)
        else:
            result_tuple = (False, len(match_list), match_list)
        return result_tuple

    class Meta:
        verbose_name = app_name.upper()+" Fossil"
        verbose_name_plural = app_name.upper()+" Fossils"
예제 #19
0
파일: instance.py 프로젝트: babacar/onadata
class Instance(models.Model, InstanceBaseClass):
    """
    Model representing a single submission to an XForm
    """

    json = JSONField(default=dict, null=False)
    xml = models.TextField()
    user = models.ForeignKey(
        User, related_name='instances', null=True, on_delete=models.SET_NULL)
    xform = models.ForeignKey(
        'logger.XForm', null=False, related_name='instances',
        on_delete=models.CASCADE)
    survey_type = models.ForeignKey(
        'logger.SurveyType', on_delete=models.PROTECT)

    # shows when we first received this instance
    date_created = models.DateTimeField(auto_now_add=True)

    # this will end up representing "date last parsed"
    date_modified = models.DateTimeField(auto_now=True)

    # this will end up representing "date instance was deleted"
    deleted_at = models.DateTimeField(null=True, default=None)
    deleted_by = models.ForeignKey(User, related_name='deleted_instances',
                                   null=True, on_delete=models.SET_NULL)

    # this will be edited when we need to create a new InstanceHistory object
    last_edited = models.DateTimeField(null=True, default=None)

    # ODK keeps track of three statuses for an instance:
    # incomplete, submitted, complete
    # we add a fourth status: submitted_via_web
    status = models.CharField(max_length=20,
                              default=u'submitted_via_web')
    uuid = models.CharField(max_length=249, default=u'', db_index=True)
    version = models.CharField(max_length=XFORM_TITLE_LENGTH, null=True)

    # store a geographic objects associated with this instance
    geom = models.GeometryCollectionField(null=True)

    # Keep track of whether all media attachments have been received
    media_all_received = models.NullBooleanField(
        _("Received All Media Attachemts"),
        null=True,
        default=True)
    total_media = models.PositiveIntegerField(_("Total Media Attachments"),
                                              null=True,
                                              default=0)
    media_count = models.PositiveIntegerField(_("Received Media Attachments"),
                                              null=True,
                                              default=0)
    checksum = models.CharField(max_length=64, null=True, blank=True,
                                db_index=True)
    # Keep track of submission reviews, only query reviews if true
    has_a_review = models.BooleanField(_("has_a_review"), default=False)

    tags = TaggableManager()

    class Meta:
        app_label = 'logger'
        unique_together = ('xform', 'uuid')

    @classmethod
    def set_deleted_at(cls, instance_id, deleted_at=timezone.now(), user=None):
        try:
            instance = cls.objects.get(id=instance_id)
        except cls.DoesNotExist:
            pass
        else:
            instance.set_deleted(deleted_at, user)

    def _check_active(self, force):
        """Check that form is active and raise exception if not.

        :param force: Ignore restrictions on saving.
        """
        # pylint: disable=no-member
        if not force and self.xform and not self.xform.downloadable:
            raise FormInactiveError()

    def _check_is_merged_dataset(self):
        """Check for merged datasets.

        Raises an exception to prevent datasubmissions
        """
        # pylint: disable=no-member
        if self.xform and self.xform.is_merged_dataset:
            raise FormIsMergedDatasetError()

    def get_expected_media(self):
        """
        Returns a list of expected media files from the submission data.
        """
        if not hasattr(self, '_expected_media'):
            # pylint: disable=no-member
            data = self.get_dict()
            media_list = []
            if 'encryptedXmlFile' in data and self.xform.encrypted:
                media_list.append(data['encryptedXmlFile'])
                if 'media' in data:
                    # pylint: disable=no-member
                    media_list.extend([i['media/file'] for i in data['media']])
            else:
                media_xpaths = (self.xform.get_media_survey_xpaths() +
                                self.xform.get_osm_survey_xpaths())
                for media_xpath in media_xpaths:
                    media_list.extend(
                        get_values_matching_key(data, media_xpath))
            # pylint: disable=attribute-defined-outside-init
            self._expected_media = list(set(media_list))

        return self._expected_media

    @property
    def num_of_media(self):
        """
        Returns number of media attachments expected in the submission.
        """
        if not hasattr(self, '_num_of_media'):
            # pylint: disable=attribute-defined-outside-init
            self._num_of_media = len(self.get_expected_media())

        return self._num_of_media

    @property
    def attachments_count(self):
        return self.attachments.filter(
            name__in=self.get_expected_media()
        ).distinct('name').order_by('name').count()

    def save(self, *args, **kwargs):
        force = kwargs.get('force')

        if force:
            del kwargs['force']

        self._check_is_merged_dataset()
        self._check_active(force)
        self._set_geom()
        self._set_json()
        self._set_survey_type()
        self._set_uuid()
        # pylint: disable=no-member
        self.version = self.json.get(VERSION, self.xform.version)

        super(Instance, self).save(*args, **kwargs)

    # pylint: disable=no-member
    def set_deleted(self, deleted_at=timezone.now(), user=None):
        if user:
            self.deleted_by = user
        self.deleted_at = deleted_at
        self.save()
        # force submission count re-calculation
        self.xform.submission_count(force_update=True)
        self.parsed_instance.save()

    def soft_delete_attachments(self, user=None):
        """
        Soft deletes an attachment by adding a deleted_at timestamp.
        """
        queryset = self.attachments.filter(
            ~Q(name__in=self.get_expected_media()))
        kwargs = {'deleted_at': timezone.now()}
        if user:
            kwargs.update({'deleted_by': user})
        queryset.update(**kwargs)
예제 #20
0
class Reservation(ModifiableModel):
    CREATED = 'created'
    CANCELLED = 'cancelled'
    CONFIRMED = 'confirmed'
    DENIED = 'denied'
    REQUESTED = 'requested'
    WAITING_FOR_PAYMENT = 'waiting_for_payment'
    STATE_CHOICES = (
        (CREATED, _('created')),
        (CANCELLED, _('cancelled')),
        (CONFIRMED, _('confirmed')),
        (DENIED, _('denied')),
        (REQUESTED, _('requested')),
        (WAITING_FOR_PAYMENT, _('waiting for payment')),
    )

    TYPE_NORMAL = 'normal'
    TYPE_BLOCKED = 'blocked'
    TYPE_CHOICES = (
        (TYPE_NORMAL, _('Normal reservation')),
        (TYPE_BLOCKED, _('Resource blocked')),
    )

    resource = models.ForeignKey('Resource',
                                 verbose_name=_('Resource'),
                                 db_index=True,
                                 related_name='reservations',
                                 on_delete=models.PROTECT)
    begin = models.DateTimeField(verbose_name=_('Begin time'))
    end = models.DateTimeField(verbose_name=_('End time'))
    duration = pgfields.DateTimeRangeField(
        verbose_name=_('Length of reservation'),
        null=True,
        blank=True,
        db_index=True)
    comments = models.TextField(null=True,
                                blank=True,
                                verbose_name=_('Comments'))
    user = models.ForeignKey(settings.AUTH_USER_MODEL,
                             verbose_name=_('User'),
                             null=True,
                             blank=True,
                             db_index=True,
                             on_delete=models.PROTECT)
    state = models.CharField(max_length=32,
                             choices=STATE_CHOICES,
                             verbose_name=_('State'),
                             default=CREATED)
    approver = models.ForeignKey(settings.AUTH_USER_MODEL,
                                 verbose_name=_('Approver'),
                                 related_name='approved_reservations',
                                 null=True,
                                 blank=True,
                                 on_delete=models.SET_NULL)
    staff_event = models.BooleanField(verbose_name=_('Is staff event'),
                                      default=False)
    type = models.CharField(blank=False,
                            verbose_name=_('Type'),
                            max_length=32,
                            choices=TYPE_CHOICES,
                            default=TYPE_NORMAL)

    # access-related fields
    access_code = models.CharField(verbose_name=_('Access code'),
                                   max_length=32,
                                   null=True,
                                   blank=True)

    # EXTRA FIELDS START HERE

    event_subject = models.CharField(max_length=200,
                                     verbose_name=_('Event subject'),
                                     blank=True)
    event_description = models.TextField(verbose_name=_('Event description'),
                                         blank=True)
    number_of_participants = models.PositiveSmallIntegerField(
        verbose_name=_('Number of participants'), blank=True, null=True)
    participants = models.TextField(verbose_name=_('Participants'), blank=True)
    host_name = models.CharField(verbose_name=_('Host name'),
                                 max_length=100,
                                 blank=True)
    reservation_extra_questions = models.TextField(
        verbose_name=_('Reservation extra questions'), blank=True)

    reserver_name = models.CharField(verbose_name=_('Reserver name'),
                                     max_length=100,
                                     blank=True)
    reserver_id = models.CharField(
        verbose_name=_('Reserver ID (business or person)'),
        max_length=30,
        blank=True)
    reserver_email_address = models.EmailField(
        verbose_name=_('Reserver email address'), blank=True)
    reserver_phone_number = models.CharField(
        verbose_name=_('Reserver phone number'), max_length=30, blank=True)
    reserver_address_street = models.CharField(
        verbose_name=_('Reserver address street'), max_length=100, blank=True)
    reserver_address_zip = models.CharField(
        verbose_name=_('Reserver address zip'), max_length=30, blank=True)
    reserver_address_city = models.CharField(
        verbose_name=_('Reserver address city'), max_length=100, blank=True)
    company = models.CharField(verbose_name=_('Company'),
                               max_length=100,
                               blank=True)
    billing_first_name = models.CharField(verbose_name=_('Billing first name'),
                                          max_length=100,
                                          blank=True)
    billing_last_name = models.CharField(verbose_name=_('Billing last name'),
                                         max_length=100,
                                         blank=True)
    billing_email_address = models.EmailField(
        verbose_name=_('Billing email address'), blank=True)
    billing_phone_number = models.CharField(
        verbose_name=_('Billing phone number'), max_length=30, blank=True)
    billing_address_street = models.CharField(
        verbose_name=_('Billing address street'), max_length=100, blank=True)
    billing_address_zip = models.CharField(
        verbose_name=_('Billing address zip'), max_length=30, blank=True)
    billing_address_city = models.CharField(
        verbose_name=_('Billing address city'), max_length=100, blank=True)

    # If the reservation was imported from another system, you can store the original ID in the field below.
    origin_id = models.CharField(verbose_name=_('Original ID'),
                                 max_length=50,
                                 editable=False,
                                 null=True)

    objects = ReservationQuerySet.as_manager()

    class Meta:
        verbose_name = _("reservation")
        verbose_name_plural = _("reservations")
        ordering = ('id', )

    def _save_dt(self, attr, dt):
        """
        Any DateTime object is converted to UTC time zone aware DateTime
        before save

        If there is no time zone on the object, resource's time zone will
        be assumed through its unit's time zone
        """
        save_dt(self, attr, dt, self.resource.unit.time_zone)

    def _get_dt(self, attr, tz):
        return get_dt(self, attr, tz)

    @property
    def begin_tz(self):
        return self.begin

    @begin_tz.setter
    def begin_tz(self, dt):
        self._save_dt('begin', dt)

    def get_begin_tz(self, tz):
        return self._get_dt("begin", tz)

    @property
    def end_tz(self):
        return self.end

    @end_tz.setter
    def end_tz(self, dt):
        """
        Any DateTime object is converted to UTC time zone aware DateTime
        before save

        If there is no time zone on the object, resource's time zone will
        be assumed through its unit's time zone
        """
        self._save_dt('end', dt)

    def get_end_tz(self, tz):
        return self._get_dt("end", tz)

    def is_active(self):
        return self.end >= timezone.now() and self.state not in (
            Reservation.CANCELLED, Reservation.DENIED)

    def is_own(self, user):
        if not (user and user.is_authenticated):
            return False
        return user == self.user

    def need_manual_confirmation(self):
        return self.resource.need_manual_confirmation

    def are_extra_fields_visible(self, user):
        # the following logic is used also implemented in ReservationQuerySet
        # so if this is changed that probably needs to be changed as well

        if self.is_own(user):
            return True
        return self.resource.can_view_reservation_extra_fields(user)

    def can_view_access_code(self, user):
        if self.is_own(user):
            return True
        return self.resource.can_view_reservation_access_code(user)

    def set_state(self, new_state, user):
        # Make sure it is a known state
        assert new_state in (Reservation.REQUESTED, Reservation.CONFIRMED,
                             Reservation.DENIED, Reservation.CANCELLED,
                             Reservation.WAITING_FOR_PAYMENT)

        old_state = self.state
        if new_state == old_state:
            if old_state == Reservation.CONFIRMED:
                reservation_modified.send(sender=self.__class__,
                                          instance=self,
                                          user=user)
            return

        if new_state == Reservation.CONFIRMED:
            self.approver = user
            reservation_confirmed.send(sender=self.__class__,
                                       instance=self,
                                       user=user)
        elif old_state == Reservation.CONFIRMED:
            self.approver = None

        user_is_staff = self.user is not None and self.user.is_staff

        # Notifications
        if new_state == Reservation.REQUESTED:
            self.send_reservation_requested_mail()
            self.send_reservation_requested_mail_to_officials()
        elif new_state == Reservation.CONFIRMED:
            if self.need_manual_confirmation():
                self.send_reservation_confirmed_mail()
            elif self.access_code:
                self.send_reservation_created_with_access_code_mail()
            else:
                if not user_is_staff:
                    # notifications are not sent from staff created reservations to avoid spam
                    self.send_reservation_created_mail()
        elif new_state == Reservation.DENIED:
            self.send_reservation_denied_mail()
        elif new_state == Reservation.CANCELLED:
            order = self.get_order()
            if order:
                if order.state == order.CANCELLED:
                    self.send_reservation_cancelled_mail()
            else:
                if user != self.user:
                    self.send_reservation_cancelled_mail()
            reservation_cancelled.send(sender=self.__class__,
                                       instance=self,
                                       user=user)

        self.state = new_state

        self.save()

    def can_modify(self, user):
        if not user:
            return False

        if self.state == Reservation.WAITING_FOR_PAYMENT:
            return False

        if self.get_order():
            return self.resource.can_modify_paid_reservations(user)

        # reservations that need manual confirmation and are confirmed cannot be
        # modified or cancelled without reservation approve permission
        cannot_approve = not self.resource.can_approve_reservations(user)
        if self.need_manual_confirmation(
        ) and self.state == Reservation.CONFIRMED and cannot_approve:
            return False

        return self.user == user or self.resource.can_modify_reservations(user)

    def can_add_comment(self, user):
        if self.is_own(user):
            return True
        return self.resource.can_access_reservation_comments(user)

    def can_view_field(self, user, field):
        if field not in RESERVATION_EXTRA_FIELDS:
            return True
        if self.is_own(user):
            return True
        return self.resource.can_view_reservation_extra_fields(user)

    def can_view_catering_orders(self, user):
        if self.is_own(user):
            return True
        return self.resource.can_view_reservation_catering_orders(user)

    def can_add_product_order(self, user):
        return self.is_own(user)

    def can_view_product_orders(self, user):
        if self.is_own(user):
            return True
        return self.resource.can_view_reservation_product_orders(user)

    def get_order(self):
        return getattr(self, 'order', None)

    def format_time(self):
        tz = self.resource.unit.get_tz()
        begin = self.begin.astimezone(tz)
        end = self.end.astimezone(tz)
        return format_dt_range(translation.get_language(), begin, end)

    def __str__(self):
        if self.state != Reservation.CONFIRMED:
            state_str = ' (%s)' % self.state
        else:
            state_str = ''
        return "%s: %s%s" % (self.format_time(), self.resource, state_str)

    def clean(self, **kwargs):
        """
        Check restrictions that are common to all reservations.

        If this reservation isn't yet saved and it will modify an existing reservation,
        the original reservation need to be provided in kwargs as 'original_reservation', so
        that it can be excluded when checking if the resource is available.
        """

        if 'user' in kwargs:
            user = kwargs['user']
        else:
            user = self.user

        user_is_admin = user and self.resource.is_admin(user)

        if self.end <= self.begin:
            raise ValidationError(
                _("You must end the reservation after it has begun"))

        # Check that begin and end times are on valid time slots.
        opening_hours = self.resource.get_opening_hours(
            self.begin.date(), self.end.date())
        for dt in (self.begin, self.end):
            days = opening_hours.get(dt.date(), [])
            day = next((day for day in days if day['opens'] is not None
                        and day['opens'] <= dt <= day['closes']), None)
            if day and not is_valid_time_slot(dt, self.resource.slot_size,
                                              day['opens']):
                raise ValidationError(
                    _("Begin and end time must match time slots"),
                    code='invalid_time_slot')

        # Check if Unit has disallow_overlapping_reservations value of True
        if (self.resource.unit.disallow_overlapping_reservations and
                not self.resource.can_create_overlapping_reservations(user)):
            reservations_for_same_unit = Reservation.objects.filter(
                user=user, resource__unit=self.resource.unit)
            valid_reservations_for_same_unit = reservations_for_same_unit.exclude(
                state=Reservation.CANCELLED)
            user_has_conflicting_reservations = valid_reservations_for_same_unit.filter(
                Q(begin__gt=self.begin, begin__lt=self.end)
                | Q(begin__lt=self.begin, end__gt=self.begin)
                | Q(begin__gte=self.begin, end__lte=self.end))

            if user_has_conflicting_reservations:
                raise ValidationError(_(
                    'This unit does not allow overlapping reservations for its resources'
                ),
                                      code='conflicting_reservation')

        original_reservation = self if self.pk else kwargs.get(
            'original_reservation', None)
        if self.resource.check_reservation_collision(self.begin, self.end,
                                                     original_reservation):
            raise ValidationError(
                _("The resource is already reserved for some of the period"))

        if not user_is_admin:
            if (self.end - self.begin) < self.resource.min_period:
                raise ValidationError(
                    _("The minimum reservation length is %(min_period)s") % {
                        'min_period': humanize_duration(
                            self.resource.min_period)
                    })
        else:
            if not (self.end - self.begin
                    ) % self.resource.slot_size == datetime.timedelta(0):
                raise ValidationError(
                    _("The minimum reservation length is %(slot_size)s") %
                    {'slot_size': humanize_duration(self.resource.slot_size)})

        if self.access_code:
            validate_access_code(self.access_code,
                                 self.resource.access_code_type)

    def get_notification_context(self,
                                 language_code,
                                 user=None,
                                 notification_type=None):
        if not user:
            user = self.user
        with translation.override(language_code):
            reserver_name = self.reserver_name
            reserver_email_address = self.reserver_email_address
            if not reserver_name and self.user and self.user.get_display_name(
            ):
                reserver_name = self.user.get_display_name()
            if not reserver_email_address and user and user.email:
                reserver_email_address = user.email
            context = {
                'resource': self.resource.name,
                'begin': localize_datetime(self.begin),
                'end': localize_datetime(self.end),
                'begin_dt': self.begin,
                'end_dt': self.end,
                'time_range': self.format_time(),
                'reserver_name': reserver_name,
                'reserver_email_address': reserver_email_address,
            }
            directly_included_fields = (
                'number_of_participants',
                'host_name',
                'event_subject',
                'event_description',
                'reserver_phone_number',
                'billing_first_name',
                'billing_last_name',
                'billing_email_address',
                'billing_phone_number',
                'billing_address_street',
                'billing_address_zip',
                'billing_address_city',
            )
            for field in directly_included_fields:
                context[field] = getattr(self, field)
            if self.resource.unit:
                context['unit'] = self.resource.unit.name
                context['unit_id'] = self.resource.unit.id
            if self.can_view_access_code(user) and self.access_code:
                context['access_code'] = self.access_code

            if notification_type in [
                    NotificationType.RESERVATION_CONFIRMED,
                    NotificationType.RESERVATION_CREATED
            ]:
                if self.resource.reservation_confirmed_notification_extra:
                    context[
                        'extra_content'] = self.resource.reservation_confirmed_notification_extra
            elif notification_type == NotificationType.RESERVATION_REQUESTED:
                if self.resource.reservation_requested_notification_extra:
                    context[
                        'extra_content'] = self.resource.reservation_requested_notification_extra
            elif notification_type in [
                    NotificationType.RESERVATION_CANCELLED,
                    NotificationType.RESERVATION_DENIED
            ]:
                if hasattr(self, 'cancel_reason'):
                    context[
                        'extra_content'] = '\n\n{}\n\n{}\n\n{}\n\n{}'.format(
                            self.cancel_reason.description,
                            self.cancel_reason.category.description_fi,
                            self.cancel_reason.category.description_en,
                            self.cancel_reason.category.description_sv)

            # Get last main and ground plan images. Normally there shouldn't be more than one of each
            # of those images.
            images = self.resource.images.filter(
                type__in=('main', 'ground_plan')).order_by('-sort_order')
            main_image = next((i for i in images if i.type == 'main'), None)
            ground_plan_image = next(
                (i for i in images if i.type == 'ground_plan'), None)

            if main_image:
                main_image_url = main_image.get_full_url()
                if main_image_url:
                    context['resource_main_image_url'] = main_image_url
            if ground_plan_image:
                ground_plan_image_url = ground_plan_image.get_full_url()
                if ground_plan_image_url:
                    context[
                        'resource_ground_plan_image_url'] = ground_plan_image_url

            order = getattr(self, 'order', None)
            if order:
                context['order'] = order.get_notification_context(
                    language_code)

        return context

    def send_reservation_mail(self,
                              notification_type,
                              user=None,
                              attachments=None):
        """
        Stuff common to all reservation related mails.

        If user isn't given use self.user.
        """
        try:
            notification_template = NotificationTemplate.objects.get(
                type=notification_type)
        except NotificationTemplate.DoesNotExist:
            return

        if getattr(self, 'order', None) and self.billing_email_address:
            email_address = self.billing_email_address
        elif user:
            email_address = user.email
        else:
            if not (self.reserver_email_address or self.user):
                return
            email_address = self.reserver_email_address or self.user.email
            user = self.user

        language = user.get_preferred_language() if user else DEFAULT_LANG
        context = self.get_notification_context(
            language, notification_type=notification_type)

        try:
            rendered_notification = notification_template.render(
                context, language)
        except NotificationTemplateException as e:
            logger.error(e, exc_info=True, extra={'user': user.uuid})
            return

        send_respa_mail(email_address, rendered_notification['subject'],
                        rendered_notification['body'],
                        rendered_notification['html_body'], attachments)

    def send_reservation_requested_mail(self):
        self.send_reservation_mail(NotificationType.RESERVATION_REQUESTED)

    def send_reservation_requested_mail_to_officials(self):
        notify_users = self.resource.get_users_with_perm(
            'can_approve_reservation')
        if len(notify_users) > 100:
            raise Exception("Refusing to notify more than 100 users (%s)" %
                            self)
        for user in notify_users:
            self.send_reservation_mail(
                NotificationType.RESERVATION_REQUESTED_OFFICIAL, user=user)

    def send_reservation_denied_mail(self):
        self.send_reservation_mail(NotificationType.RESERVATION_DENIED)

    def send_reservation_confirmed_mail(self):
        reservations = [self]
        ical_file = build_reservations_ical_file(reservations)
        ics_attachment = ('reservation.ics', ical_file, 'text/calendar')
        attachments = [ics_attachment] + self.get_resource_email_attachments()

        self.send_reservation_mail(NotificationType.RESERVATION_CONFIRMED,
                                   attachments=attachments)

    def send_reservation_cancelled_mail(self):
        self.send_reservation_mail(NotificationType.RESERVATION_CANCELLED)

    def send_reservation_created_mail(self):
        reservations = [self]
        ical_file = build_reservations_ical_file(reservations)
        ics_attachment = ('reservation.ics', ical_file, 'text/calendar')
        attachments = [ics_attachment] + self.get_resource_email_attachments()

        self.send_reservation_mail(NotificationType.RESERVATION_CREATED,
                                   attachments=attachments)

    def send_reservation_created_with_access_code_mail(self):
        reservations = [self]
        ical_file = build_reservations_ical_file(reservations)
        ics_attachment = ('reservation.ics', ical_file, 'text/calendar')
        attachments = [ics_attachment] + self.get_resource_email_attachments()
        self.send_reservation_mail(
            NotificationType.RESERVATION_CREATED_WITH_ACCESS_CODE,
            attachments=attachments)

    def get_resource_email_attachments(self):
        attachments = []
        for attachment in self.resource.attachments.all():
            file_name = os.path.basename(attachment.attachment_file.name)
            file_type = mimetypes.guess_type(attachment.attachment_file.url)[0]
            if not file_type:
                continue
            attachments.append(
                (file_name, attachment.attachment_file.read(), file_type))

        return attachments

    def send_access_code_created_mail(self):
        self.send_reservation_mail(
            NotificationType.RESERVATION_ACCESS_CODE_CREATED)

    def save(self, *args, **kwargs):
        self.duration = DateTimeTZRange(self.begin, self.end, '[)')

        if not self.access_code:
            access_code_type = self.resource.access_code_type
            if self.resource.is_access_code_enabled(
            ) and self.resource.generate_access_codes:
                self.access_code = generate_access_code(access_code_type)

        return super().save(*args, **kwargs)
예제 #21
0
class Community(UniqueSlugMixin, models.Model):
    name = models.CharField(_("Name"), max_length=255)
    slug = models.SlugField(_("Slug"), null=False, blank=True,
                            unique=True)  # populated by UniqueSlugMixin
    tags = TaggableManager(
        _('Tags'),
        through=TaggedCommunity,
        related_name='_tags',
        blank=True,
        help_text=
        _('Add some keywords that define your community. This way your profile can be found by in the search. People might be interested in your technologies, structures or experiences.'
          ))
    skills = TaggableManager(
        _('Skills'),
        through=TaggedSkills,
        related_name='_skills',
        blank=True,
        help_text=
        _('Skills that people can learn by volunteering or staying in your community'
          ))
    description = models.TextField(_("Description"), blank=True)
    vision = models.TextField(_("What brings this community together?"),
                              blank=True)
    accomodation = models.TextField(
        _("Accomodation for Guests"),
        help_text=
        _('Where can your visitors sleep? Do you have space for a bus, tents? How is the indoor sleeping situation? Do you have matresses, a couch? Do you have a donations or a pricing model? Required daily working amount or epxeriences?'
          ),
        blank=True)
    website = models.URLField(_('link of your communities website'),
                              max_length=250,
                              blank=True,
                              null=True)
    telephone = models.CharField(_('telephone'),
                                 max_length=255,
                                 blank=True,
                                 null=True)
    video = EmbedVideoField(
        verbose_name=_('Video'),
        help_text=_('Link to a video showing your community'),
        max_length=255,
        blank=True,
        null=True)
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)
    owner = models.ForeignKey(
        User,
        on_delete=models.SET_NULL,
        related_name="communities",
        verbose_name=_("Owner"),
        null=True,
    )
    inhabitants = models.CharField(
        _('how many people live in your community?'),
        max_length=255,
        null=True,
        blank=True)
    children = models.PositiveIntegerField(
        _('how many children live at your place?'),
        null=True,
        blank=True,
        default=0)
    COMMUNITY_STATUS_PLANNING = 'p'
    COMMUNITY_STATUS_STARTING = 's'
    COMMUNITY_STATUS_ESTABLISHED = 'e'
    COMMUNITY_STATUS_LAND = 'l'
    COMMUNITY_STATUS_CHOICES = (
        (COMMUNITY_STATUS_PLANNING,
         _('Future Project (looking for co-founders)')),
        (COMMUNITY_STATUS_STARTING, _('Starting Project (first years)')),
        (COMMUNITY_STATUS_ESTABLISHED, _('Established (+4 years)')),
    )
    status = models.CharField(_('Project status'),
                              max_length=2,
                              blank=True,
                              choices=COMMUNITY_STATUS_CHOICES,
                              default=COMMUNITY_STATUS_ESTABLISHED)

    COMMUNITY_TYPE_ECOVILLAGE = 'e'
    COMMUNITY_TYPE_COMUNE = 'c'
    COMMUNITY_TYPE_HOUSEPROJECT = 'h'
    COMMUNITY_TYPE_FARM = 'f'
    COMMUNITY_TYPE_PROJECT = 'p'
    COMMUNITY_TYPE_CHOICES = (
        (COMMUNITY_TYPE_ECOVILLAGE, _('Ecovillage')),
        (COMMUNITY_TYPE_COMUNE, _('Commune')),
        (COMMUNITY_TYPE_HOUSEPROJECT, _('Houseproject')),
        (COMMUNITY_TYPE_FARM, _('Permaculture Farm')),
        (COMMUNITY_TYPE_PROJECT, _('Place for Projects')),
    )
    type = models.CharField(_('Type of community'),
                            max_length=2,
                            blank=True,
                            choices=COMMUNITY_TYPE_CHOICES,
                            default=COMMUNITY_TYPE_ECOVILLAGE)

    # location_name = models.CharField(
    #     _("Location"), null=True, blank=True, max_length=255
    # )
    # location = models.PointField(
    #     _("Geo Location"), null=True, blank=True, geography=True
    # )

    def __str__(self):
        return self.name

    def get_absolute_url(self) -> str:
        return reverse("communities:detail", kwargs={"slug": self.slug})

    class Meta:
        verbose_name = _("Community")
        verbose_name_plural = _("Communities")
        ordering = ["name"]
예제 #22
0
class Locality(UpdateMixin, ChangesetMixin):
    """
    A Locality is uniquely defined by an *uuid* attribute. Attribute *geom*
    stores geometry as a point object. *upstream_id* is used to preserve link
    to the originating dataset which is used to find and update a Locality on
    any reoccurring data imports.

    A Locality is in a *Domain* and data values for Attributes, to be exact,
    their Specifications, are defined through *Value*
    """

    domain = models.ForeignKey('Domain')
    uuid = models.TextField(unique=True)
    upstream_id = models.TextField(null=True, unique=True)
    geom = models.PointField(srid=4326)
    specifications = models.ManyToManyField('Specification', through='Value')
    name = models.TextField()
    source = models.TextField(default='healthsites.io')

    # completeness is a big calculation
    # so it has to be an field
    completeness = models.FloatField(null=True, default=0.0)
    is_master = models.BooleanField(default=True)

    objects = PassThroughGeoManager.for_queryset_class(LocalitiesQuerySet)()
    tracker = FieldTracker()

    def before_save(self, *args, **kwargs):
        # make sure that we don't allow uuid modifications
        if self.tracker.previous('uuid') and self.tracker.has_changed('uuid'):
            self.uuid = self.tracker.previous('uuid')

    def _get_attr_map(self):
        return (self.domain.specification_set.order_by('id').values(
            'id', 'attribute__key'))

    def set_geom(self, lon, lat):
        """
        Helper method to set Locality geometry
        """

        self.geom.set_x(lon)
        self.geom.set_y(lat)

    def set_values(self, changed_data, social_user, changeset=None):
        """
        Set values for a Locality which are defined by Specifications

        Once all of values are set, 'SIG_locality_values_updated' signal will
        be triggered to update FullTextSearch index for this Locality
        """
        special_key = [
            'scope_of_service', "ancillary_services", "activities",
            "inpatient_service", "staff"
        ]
        attrs = self._get_attr_map()

        tmp_changeset = changeset

        changed_values = []
        for key, data in changed_data.iteritems():
            if key in special_key:
                data = data.replace(",", "|")
                data = data.replace("| ", "|")
            # try to match key from changed items with a key from attr_map
            attr_list = [
                attr for attr in attrs if attr['attribute__key'] == key
            ]

            if attr_list:
                # get specification id for specific key
                spec_id = attr_list[0]['id']

                # update or create new values
                try:
                    obj = self.value_set.get(specification_id=spec_id)
                    _created = False
                except Value.DoesNotExist:
                    # in case there is no value for the specification, create
                    obj = Value()
                    obj.locality = self
                    obj.specification_id = spec_id
                    _created = True

                # set data
                obj.data = data

                # check if Value.data actually changed, and save if it did
                if obj.tracker.changed():
                    if not (tmp_changeset):
                        tmp_changeset = Changeset.objects.create(
                            social_user=social_user)
                    obj.changeset = tmp_changeset
                    obj.save()
                    changed_values.append((obj, _created))
                else:
                    # nothing changed, don't save the value
                    pass
            else:
                # attr_id was not found (maybe a bad attribute)
                LOG.warning('Locality %s has no attribute key %s', self.pk,
                            key)

        # send values_updated signal
        signals.SIG_locality_values_updated.send(sender=self.__class__,
                                                 instance=self)

        # calculate completeness
        if changed_values:
            self.completeness = self.calculate_completeness()
            self.save()

        return changed_values

    def repr_dict(self, clean=False):
        """
        Basic locality representation, as a dictionary
        """

        dict = {
            u'uuid': self.uuid,
            u'upstream': self.upstream_id,
            u'source': self.source,
            u'name': self.name,
            u'geom': (self.geom.x, self.geom.y),
            u'version': self.version,
            u'date_modified': self.changeset.created,
            u'completeness': '%s%%' % format(self.completeness, '.2f'),
        }

        dict['values'] = {}
        for val in self.value_set.select_related().exclude(
                data__isnull=True).exclude(data__exact=''):
            if clean:
                # clean if empty
                temp = val.data.replace("|", "")
                if len(temp) == 0:
                    val.data = ""
                # clean data
                val.data = val.data.replace("|", ",")
                val.specification.attribute.key = val.specification.attribute.key.replace(
                    "_", "-")
                cleaned_data = val.data.replace(",", "")
                if len(cleaned_data) > 0:
                    dict['values'][val.specification.attribute.key] = val.data
            else:
                dict['values'][val.specification.attribute.key] = val.data

        try:
            site = Site.objects.get(name=dict[u'source'])
            dict[u'source_url'] = site.domain
        except Site.DoesNotExist:
            pass

        # exclusive for open street map
        if "openstreetmap" in self.upstream_id.lower():
            osm_whole_id = self.upstream_id.split(u"¶")
            if len(osm_whole_id) > 0:
                osm_whole_id = osm_whole_id[1]
                identifier = osm_whole_id[0]
                osm_id = osm_whole_id[1:]
                if identifier == 'n':
                    url = 'http://www.openstreetmap.org/node/' + osm_id
                elif identifier == 'r':
                    url = 'http://www.openstreetmap.org/relation/' + osm_id
                elif identifier == 'w':
                    url = 'http://www.openstreetmap.org/way/' + osm_id

                if url:
                    dict['source_url'] = url
        return dict

    def is_type(self, value):
        if value != "":
            try:
                self.value_set.filter(
                    specification__attribute__key='type').get(data=value)
                return True
            except Exception as e:
                return False
        return True

    def calculate_completeness(self):
        DEFAULT_VALUE = 4  # GUID & GEOM & NAME & DATA SOURCE
        global_attr = attributes_availables['global']
        specific_attr = attributes_availables['hospital']
        for key in attributes_availables.keys():
            try:
                self.value_set.filter(
                    specification__attribute__key='type').get(
                        data__icontains=key)
                specific_attr = attributes_availables[key]
            except Value.DoesNotExist:
                continue

        values = self.repr_dict()['values']
        counted_value = DEFAULT_VALUE
        max_value = len(global_attr) + len(specific_attr) + DEFAULT_VALUE

        for attr in global_attr + specific_attr:
            if attr in values:
                data = values[attr]
                if len(data.replace("-", "").replace("|", "").strip()) != 0:
                    counted_value += 1

        return (counted_value + 0.0) / (max_value + 0.0) * 100

    def prepare_for_fts(self):
        """
        Retrieve and group *Value* objects, for this Locality, based on their
        FTS ordering (defined by *Specification*)
        """

        data_values = itertools.groupby(
            self.value_set.order_by('specification__fts_rank').values_list(
                'specification__fts_rank', 'data'), lambda x: x[0])

        return {k: ' '.join([x[1] for x in v]) for k, v in data_values}

    def update_what3words(self, user, changeset):
        from utils import get_what_3_words
        what3words = get_what_3_words(self.geom)
        if what3words != "":
            self.set_values({'what3words': what3words}, user, changeset)

    def get_synonyms(self):
        synonyms = SynonymLocalities.objects.get(locality=self)
        return synonyms

    def __unicode__(self):
        return u'{}'.format(self.id)
예제 #23
0
class Attribution(models.Model):
    attribution = models.TextField()
예제 #24
0
class Shipment(models.Model):
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

    shipment_id = models.CharField(
        max_length=32, null=True, blank=True,
        help_text="Identifier for shipment. Not same as database primary key.")
    carrier = models.ForeignKey(
        'GenericCompany', null=True, blank=True,
        related_name='active_shipments')
    carrier_is_approved = models.BooleanField(default=False)
    owner = models.ForeignKey('GenericCompany', blank=True, null=True)
    owner_user = models.ForeignKey('GenericUser', blank=True, null=True)
    comments = models.TextField(null=True, blank=True)
    next_trip_dist_update = models.DateTimeField(auto_now_add=True)
    delivery_status = models.IntegerField(
        choices=DeliveryStatus.CHOICES, default=DeliveryStatus.OPEN)
    payout_info = models.OneToOneField('ShipmentPayout', null=True)
    bol_number = models.CharField(max_length=100, null=True, blank=True)

    first_location = models.OneToOneField(
        'ShipmentLocation', null=True, blank=True, on_delete=models.SET_NULL,
        related_name='shipment_first_location', help_text='Read only')
    last_location = models.OneToOneField(
        'ShipmentLocation', null=True, blank=True, on_delete=models.SET_NULL,
        related_name='shipment_last_location', help_text='Read only')

    carrier_assignment = models.OneToOneField(
        'ShipmentCarrierAssignment', null=True, blank=True,
        on_delete=models.SET_NULL)
    driver_assignment = models.OneToOneField(
        'ShipmentDriverAssignment', null=True, blank=True,
        on_delete=models.SET_NULL)
    objects = models.GeoManager()
    actives = ActiveShipmentManager()

    @property
    def location_count(self):
        return self.locations.count()

    @property
    def locations_completed(self):
        return self.locations.exclude(arrival_time__isnull=True).count()

    @property
    def upcoming_location(self):
        # First location that doesnt have an arrival_time
        for l in self.locations.all():
            if l.arrival_time is None:
                return l
        return None

    @property
    def first_pickup_occured(self):
        # Dont use self.first_location to avoid invalid cached instances
        count = self.locations.count()
        if count and self.locations.all()[0].arrival_time:
            return True
        else:
            return False

    @property
    def last_delivery_occured(self):
        # Dont use self.last_location to avoid invalid cached instances
        count = self.locations.count()
        if count and self.locations.all()[count-1].arrival_time:
            return True
        else:
            return False

    @property
    def trip_distance(self):
        distance = 0
        locations = self.locations.all()
        for index, location in enumerate(locations):
            if (len(locations) > 0 and index < len(locations)-1 and
                    location.distance_to_next_location is None):
                return 0
            else:
                distance = distance + location.distance_to_next_location
        return distance

    @property
    def carrier_driver(self):
        from .generic_user import UserType
        if self.carrier and (
                self.carrier.owner.user_type == UserType.CARRIER_DRIVER or
                self.carrier.owner.user_type == UserType.CARRIER_MANAGER):
            return self.carrier.owner
        else:
            return None

    @property
    def equipmenttags(self):
        return EquipmentTag.objects.filter(
            assignee_content_type=ContentType.objects.get(model='shipment'),
            assignee_id=self.id)

    @property
    def empty_required_fields(self):
        required_fields = ['payout_info']
        return get_empty_required_fields(self, required_fields)

    @property
    def assigned_companies_count(self):
        return self.shipmentassignment_set.filter(
            assignee_content_type=ContentType.objects.get(
                model='genericcompany')).count()

    @property
    def pending_requests_count(self):
        # TODO: Use ShipmentRequests model
        return 1 if self.carrier and not self.carrier_is_approved else 0

    @property
    def assigned_carrier(self):
        if self.carrier_assignment and self.carrier_assignment.assignment:
            return self.carrier_assignment.assignment.assignee
        else:
            return None

    @property
    def assigned_driver(self):
        if (self.driver_assignment and
                self.driver_assignment.assignment):
            return self.driver_assignment.assignment.assignee
        else:
            return None

    def __unicode__(self):
        return self.shipment_id

    class Meta:
        ordering = ('-pk',)
        # For ShipmentAssignment (view shipment is not a default permission)
        permissions = (
            ('view_shipment', 'View Shipment'),
        )
예제 #25
0
class ModelWithDataBlob(models.Model):
    data = models.TextField(default='{}')

    class Meta:
        abstract = True
예제 #26
0
class ITSystem(tracking.CommonFields):
    STATUS_CHOICES = ((0, "Production"), (1, "Development"),
                      (2, "Production (Legacy)"), (3, "Decommissioned"),
                      (4, "Unknown"))
    ACCESS_CHOICES = ((1, 'Public Internet'), (2, 'Authenticated Extranet'),
                      (3, 'Corporate Network'), (4,
                                                 'Local System (Networked)'),
                      (5, 'Local System (Standalone)'))
    AUTHENTICATION_CHOICES = ((1, 'Domain Credentials'), (2, 'Single Sign On'),
                              (3, 'Externally Managed'))
    name = models.CharField(max_length=128, unique=True)
    system_id = models.CharField(max_length=16, unique=True)
    acronym = models.CharField(max_length=16, null=True, blank=True)
    status = models.PositiveSmallIntegerField(choices=STATUS_CHOICES,
                                              default=4)
    status_display = models.CharField(max_length=128,
                                      null=True,
                                      editable=False)
    description = models.TextField(blank=True)
    devices = models.ManyToManyField(Device, blank=True)
    owner = models.ForeignKey(tracking.DepartmentUser,
                              on_delete=models.PROTECT,
                              null=True,
                              related_name="systems_owned",
                              help_text="Application owner")
    custodian = models.ForeignKey(tracking.DepartmentUser,
                                  on_delete=models.PROTECT,
                                  null=True,
                                  related_name="systems_custodianed",
                                  help_text="Appication custodian")
    data_custodian = models.ForeignKey(tracking.DepartmentUser,
                                       on_delete=models.PROTECT,
                                       related_name="systems_data_custodianed",
                                       null=True,
                                       blank=True)
    preferred_contact = models.ForeignKey(
        tracking.DepartmentUser,
        on_delete=models.PROTECT,
        related_name="systems_preferred_contact",
        null=True,
        blank=True)
    link = models.CharField(max_length=2048,
                            null=True,
                            blank=True,
                            help_text="URL to Application itself")
    documentation = models.CharField(max_length=2048,
                                     null=True,
                                     blank=True,
                                     help_text="URL to Documentation")
    status_html = models.CharField(max_length=2048,
                                   null=True,
                                   blank=True,
                                   help_text="URL to status/uptime info")
    authentication = models.PositiveSmallIntegerField(
        choices=AUTHENTICATION_CHOICES, default=1)
    authentication_display = models.CharField(max_length=128,
                                              null=True,
                                              editable=False)
    access = models.PositiveSmallIntegerField(choices=ACCESS_CHOICES,
                                              default=3)
    access_display = models.CharField(max_length=128,
                                      null=True,
                                      editable=False)
    request_access = models.TextField(blank=True)
    process = models.ForeignKey(Process,
                                on_delete=models.PROTECT,
                                null=True,
                                blank=True)
    function = models.ForeignKey(Function,
                                 on_delete=models.PROTECT,
                                 null=True,
                                 blank=True)

    def description_html(self):
        return mark_safe(self.description)

    def save(self, *args, **kwargs):
        if not self.system_id:
            self.system_id = "S{0:03d}".format(
                ITSystem.objects.order_by("-pk").first().pk + 1)
        self.status_display = self.get_status_display()
        self.authentication_display = self.get_authentication_display()
        if not self.link:  # systems with no link default to device
            self.access = 4
        self.access_display = self.get_access_display()
        super(ITSystem, self).save(*args, **kwargs)

    def __str__(self):
        return self.name

    class Meta:
        verbose_name = "IT System"
예제 #27
0
class Event(MPTTModel, BaseModel, SchemalessFieldMixin, ReplacedByMixin):
    jsonld_type = "Event/LinkedEvent"
    objects = BaseTreeQuerySet.as_manager()

    """
    eventStatus enumeration is based on http://schema.org/EventStatusType
    """

    class Status:
        SCHEDULED = 1
        CANCELLED = 2
        POSTPONED = 3
        RESCHEDULED = 4
    # Properties from schema.org/Event
    STATUSES = (
        (Status.SCHEDULED, "EventScheduled"),
        (Status.CANCELLED, "EventCancelled"),
        (Status.POSTPONED, "EventPostponed"),
        (Status.RESCHEDULED, "EventRescheduled"),
    )

    class SuperEventType:
        RECURRING = 'recurring'
        UMBRELLA = 'umbrella'

    SUPER_EVENT_TYPES = (
        (SuperEventType.RECURRING, _('Recurring')),
        (SuperEventType.UMBRELLA, _('Umbrella event')),
    )

    # Properties from schema.org/Thing
    info_url = models.URLField(verbose_name=_('Event home page'), blank=True, null=True, max_length=1000)
    description = models.TextField(verbose_name=_('Description'), blank=True, null=True)
    short_description = models.TextField(verbose_name=_('Short description'), blank=True, null=True)

    # Properties from schema.org/CreativeWork
    date_published = models.DateTimeField(verbose_name=_('Date published'), null=True, blank=True)
    # headline and secondary_headline are for cases where
    # the original event data contains a title and a subtitle - in that
    # case the name field is combined from these.
    #
    # secondary_headline is mapped to schema.org alternative_headline
    # and is used for subtitles, that is for
    # secondary, complementary headlines, not "alternative" headlines
    headline = models.CharField(verbose_name=_('Headline'), max_length=255, null=True, db_index=True)
    secondary_headline = models.CharField(verbose_name=_('Secondary headline'), max_length=255,
                                          null=True, db_index=True)
    provider = models.CharField(verbose_name=_('Provider'), max_length=512, null=True)
    provider_contact_info = models.CharField(verbose_name=_("Provider's contact info"),
                                             max_length=255, null=True, blank=True)
    publisher = models.ForeignKey('django_orghierarchy.Organization', verbose_name=_('Publisher'), db_index=True,
                                  on_delete=models.PROTECT, related_name='published_events')

    # Status of the event itself
    event_status = models.SmallIntegerField(verbose_name=_('Event status'), choices=STATUSES,
                                            default=Status.SCHEDULED)

    # Whether or not this data about the event is ready to be viewed by the general public.
    # DRAFT means the data is considered incomplete or is otherwise undergoing refinement --
    # or just waiting to be published for other reasons.
    publication_status = models.SmallIntegerField(
        verbose_name=_('Event data publication status'), choices=PUBLICATION_STATUSES,
        default=PublicationStatus.PUBLIC)

    location = models.ForeignKey(Place, related_name='events', null=True, blank=True, on_delete=models.PROTECT)
    location_extra_info = models.CharField(verbose_name=_('Location extra info'),
                                           max_length=400, null=True, blank=True)

    start_time = models.DateTimeField(verbose_name=_('Start time'), null=True, db_index=True, blank=True)
    end_time = models.DateTimeField(verbose_name=_('End time'), null=True, db_index=True, blank=True)
    has_start_time = models.BooleanField(default=True)
    has_end_time = models.BooleanField(default=True)

    audience_min_age = models.SmallIntegerField(verbose_name=_('Minimum recommended age'),
                                                blank=True, null=True, db_index=True)
    audience_max_age = models.SmallIntegerField(verbose_name=_('Maximum recommended age'),
                                                blank=True, null=True, db_index=True)

    super_event = TreeForeignKey('self', null=True, blank=True,
                                 on_delete=models.SET_NULL, related_name='sub_events')

    super_event_type = models.CharField(max_length=255, blank=True, null=True, db_index=True,
                                        default=None, choices=SUPER_EVENT_TYPES)

    in_language = models.ManyToManyField(Language, verbose_name=_('In language'), related_name='events', blank=True)

    images = models.ManyToManyField(Image, related_name='events', blank=True)

    deleted = models.BooleanField(default=False, db_index=True)

    replaced_by = models.ForeignKey('Event', on_delete=models.SET_NULL, related_name='aliases', null=True, blank=True)

    # Custom fields not from schema.org
    keywords = models.ManyToManyField(Keyword, related_name='events')
    audience = models.ManyToManyField(Keyword, related_name='audience_events', blank=True)

    class Meta:
        verbose_name = _('event')
        verbose_name_plural = _('events')

    class MPTTMeta:
        parent_attr = 'super_event'

    def save(self, *args, **kwargs):
        if self._has_circular_replacement():
            raise ValidationError(_("Trying to replace this event with an event that is replaced by this event. "
                                    "Please refrain from creating circular replacements and "
                                    "remove one of the replacements."))

        if self.replaced_by and not self.deleted:
            self.deleted = True
            logger.warning("Event replaced without soft deleting. Soft deleting automatically", extra={'event': self})

        # needed to cache location event numbers
        old_location = None

        # needed for notifications
        old_publication_status = None
        old_deleted = None
        created = True

        if self.id:
            try:
                event = Event.objects.get(id=self.id)
                created = False
                old_location = event.location
                old_publication_status = event.publication_status
                old_deleted = event.deleted
            except Event.DoesNotExist:
                pass

        # drafts may not have times set, so check that first
        start = getattr(self, 'start_time', None)
        end = getattr(self, 'end_time', None)
        if start and end:
            if start > end:
                raise ValidationError({'end_time': _('The event end time cannot be earlier than the start time.')})

        if (self.keywords.filter(deprecated=True) or self.audience.filter(deprecated=True)) and (
                not self.deleted):
            raise ValidationError({'keywords': _("Trying to save event with deprecated keywords " +
                                                 str(self.keywords.filter(deprecated=True).values('id')) + " or " +
                                                 str(self.audience.filter(deprecated=True).values('id')) +
                                                 ". Please use up-to-date keywords.")})

        super(Event, self).save(*args, **kwargs)

        # needed to cache location event numbers
        if not old_location and self.location:
            Place.objects.filter(id=self.location.id).update(n_events_changed=True)
        if old_location and not self.location:
            # drafts (or imported events) may not always have location set
            Place.objects.filter(id=old_location.id).update(n_events_changed=True)
        if old_location and self.location and old_location != self.location:
            Place.objects.filter(id__in=(old_location.id, self.location.id)).update(n_events_changed=True)

        # send notifications
        if old_publication_status == PublicationStatus.DRAFT and self.publication_status == PublicationStatus.PUBLIC:
            self.send_published_notification()
        if self.publication_status == PublicationStatus.DRAFT and (old_deleted is False and self.deleted is True):
            self.send_deleted_notification()
        if created and self.publication_status == PublicationStatus.DRAFT:
            self.send_draft_posted_notification()

    def __str__(self):
        name = ''
        languages = [lang[0] for lang in settings.LANGUAGES]
        for lang in languages:
            lang = lang.replace('-', '_')  # to handle complex codes like e.g. zh-hans
            s = getattr(self, 'name_%s' % lang, None)
            if s:
                name = s
                break
        val = [name, '(%s)' % self.id]
        dcount = self.get_descendant_count()
        if dcount > 0:
            val.append(u" (%d children)" % dcount)
        else:
            val.append(str(self.start_time))
        return u" ".join(val)

    def is_admin(self, user):
        if user.is_superuser:
            return True
        else:
            return user.is_admin(self.publisher)

    def can_be_edited_by(self, user):
        """Check if current event can be edited by the given user"""
        if user.is_superuser:
            return True
        return user.can_edit_event(self.publisher, self.publication_status)

    def soft_delete(self, using=None):
        self.deleted = True
        self.save(update_fields=("deleted",), using=using, force_update=True)

    def undelete(self, using=None):
        self.deleted = False
        self.save(update_fields=("deleted",), using=using, force_update=True)

    def _send_notification(self, notification_type, recipient_list, request=None):
        if len(recipient_list) == 0:
            logger.warning("No recipients for notification type '%s'" % notification_type, extra={'event': self})
            return
        context = {'event': self}
        try:
            rendered_notification = render_notification_template(notification_type, context)
        except NotificationTemplateException as e:
            logger.error(e, exc_info=True, extra={'request': request})
            return
        try:
            send_mail(
                rendered_notification['subject'],
                rendered_notification['body'],
                'noreply@%s' % Site.objects.get_current().domain,
                recipient_list,
                html_message=rendered_notification['html_body']
            )
        except SMTPException as e:
            logger.error(e, exc_info=True, extra={'request': request, 'event': self})

    def _get_author_emails(self):
        author_emails = []
        author = self.created_by
        if author and author.email:
            author_emails.append(author.email)
        return author_emails

    def send_deleted_notification(self, request=None):
        recipient_list = self._get_author_emails()
        self._send_notification(NotificationType.UNPUBLISHED_EVENT_DELETED, recipient_list, request)

    def send_published_notification(self, request=None):
        recipient_list = self._get_author_emails()
        self._send_notification(NotificationType.EVENT_PUBLISHED, recipient_list, request)

    def send_draft_posted_notification(self, request=None):
        recipient_list = []
        for admin in self.publisher.admin_users.all():
            if admin.email:
                recipient_list.append(admin.email)
        self._send_notification(NotificationType.DRAFT_POSTED, recipient_list, request)
예제 #28
0
class Backup(tracking.CommonFields):
    ROLE_CHOICES = ((0, "Generic Server"), (1, "Domain Controller"),
                    (2, "Database Server"), (3, "Application Host"),
                    (4, "Management Server"), (5, "Site Server"),
                    (6, "File Server"), (7, "Print Server"),
                    (8, "Block Storage Server"), (9, "Email Server"),
                    (10, "Network Device"))
    STATUS_CHOICES = ((0, "Production"), (1, "Pre-Production"), (2, "Legacy"),
                      (3, "Decommissioned"))
    SCHEDULE_CHOICES = ((0, "Manual"), (1, "Point in time, 7 day retention"),
                        (2, "Daily, 7 day retention"),
                        (3, "Daily, 30 day retention"),
                        (4, "Weekly, 1 month retention"))
    system = models.OneToOneField(Hardware)
    operating_system = models.CharField(max_length=120)
    parent_host = models.ForeignKey(Hardware,
                                    on_delete=models.PROTECT,
                                    null=True,
                                    blank=True,
                                    related_name="host")
    role = models.PositiveSmallIntegerField(choices=ROLE_CHOICES, default=0)
    status = models.PositiveSmallIntegerField(choices=STATUS_CHOICES,
                                              default=0)
    database_backup = models.CharField(
        max_length=2048,
        null=True,
        blank=True,
        help_text="URL to Database backup/restore/logs info")
    database_schedule = models.PositiveSmallIntegerField(
        choices=SCHEDULE_CHOICES, default=0)
    filesystem_backup = models.CharField(
        max_length=2048,
        null=True,
        blank=True,
        help_text="URL to Filesystem backup/restore/logs info")
    filesystem_schedule = models.PositiveSmallIntegerField(
        choices=SCHEDULE_CHOICES, default=0)
    appdata_backup = models.CharField(
        max_length=2048,
        null=True,
        blank=True,
        help_text="URL to Application Data backup/restore/logs info")
    appdata_schedule = models.PositiveSmallIntegerField(
        choices=SCHEDULE_CHOICES, default=0)
    appconfig_backup = models.CharField(
        max_length=2048,
        null=True,
        blank=True,
        help_text="URL to Config for App/Server")
    appconfig_schedule = models.PositiveSmallIntegerField(
        choices=SCHEDULE_CHOICES, default=0)
    os_backup = models.CharField(max_length=2048,
                                 null=True,
                                 blank=True,
                                 help_text="URL to Build Documentation")
    os_schedule = models.PositiveSmallIntegerField(choices=SCHEDULE_CHOICES,
                                                   default=0)
    last_tested = models.DateField(null=True,
                                   blank=True,
                                   help_text="Last tested date")
    test_schedule = models.PositiveSmallIntegerField(
        default=12, help_text="Test Schedule in Months, 0 for never")
    comment = models.TextField(blank=True)

    def next_test_date(self):
        if self.test_schedule == 0:
            return "Doesn't require testing"
        if not self.last_tested:
            return "NEVER TESTED"
        else:
            return self.last_tested + relativedelta(months=self.test_schedule)

    def test_overdue(self):
        if self.test_schedule == 0:
            return False
        if not self.last_tested:
            return True
        return self.next_test_date() < timezone.now().date()

    def __str__(self):
        return "{} ({})".format(
            self.system.name.split(".")[0], self.get_status_display())

    class Meta:
        ordering = ("system__name", )
예제 #29
0
class Charte(models.Model):
    region = models.TextField()
    ancienne_region = models.TextField()
    n_dept = models.TextField()
    nom_dept = models.TextField()
    code_insee = models.TextField()
    ville = models.TextField()
    commune = models.ForeignKey(Commune, null=True)
    nom_ecoquartier = models.TextField()
    population = models.TextField()
    commune_rurale_ou_urbaine = models.TextField()
    situation_territoriale = models.TextField()
    situation_du_quartier = models.TextField()
    type_d_operation = models.TextField()
    nombre_d_habitants_dans_le_quartier = models.TextField()
    nombre_de_logements = models.TextField()
    nombre_de_logements_sociaux = models.TextField()
    superficie_du_quartier_ha = models.TextField()
    epa = models.TextField()
    anru = models.TextField()
    pnr = models.TextField()
    aap_2009 = models.TextField()
    aap_2011 = models.TextField()
    charte = models.TextField()
    candidat_2013 = models.TextField()
    resultats_2013 = models.TextField()
    candidat_2014 = models.TextField()
    resultats_2014 = models.TextField()
    candidat_2015 = models.TextField()
    resultats_2015 = models.TextField()
    dotation_evaluation = models.TextField()
    commentaires = models.TextField()
    candidat_potentiel_reperage_ad4_2017 = models.TextField()
    candidat_2016 = models.TextField()
    avancement_dans_la_demarche = models.TextField()
    contact_1_mail = models.TextField()
    contact_1_titre = models.TextField()
    contact_1_prenom_nom = models.TextField()
    contact_2_mail = models.TextField()
    contact_2_titre = models.TextField()
    contact_2_prenom_nom = models.TextField()
    contact_3_mail = models.TextField()
    contact_3_titre = models.TextField()
    contact_3_prenom_nom = models.TextField()
    contact_4_mai = models.TextField()
    contact_4_titre = models.TextField()
    contact_4_prenom_nom = models.TextField()
    contact_5_mail = models.TextField()
    contact_5_titre = models.TextField()
    contact_5_prenom_nom = models.TextField()
    contact_6_mail = models.TextField()
    contact_6_titre = models.TextField()
    contact_6_prenom_nom = models.TextField()
    contact_7_mail = models.TextField()
    contact_7_titre = models.TextField()
    contact_7_prenom_nom = models.TextField()
예제 #30
0
class Provider(NameInCurrentLanguageMixin, models.Model):
    name_en = models.CharField(
        # Translators: Provider name
        _("name in English"),
        max_length=256,  # Length is a guess
        default='',
        blank=True,
        validators=[blank_or_at_least_one_letter])
    name_ar = models.CharField(
        # Translators: Provider name
        _("name in Arabic"),
        max_length=256,  # Length is a guess
        default='',
        blank=True,
        validators=[blank_or_at_least_one_letter])
    name_fr = models.CharField(
        # Translators: Provider name
        _("name in French"),
        max_length=256,  # Length is a guess
        default='',
        blank=True,
        validators=[blank_or_at_least_one_letter])
    type = models.ForeignKey(
        ProviderType,
        verbose_name=_("type"),
    )
    phone_number = models.CharField(
        _("phone number"),
        max_length=20,
        validators=[RegexValidator(settings.PHONE_NUMBER_REGEX)])
    website = models.URLField(
        _("website"),
        blank=True,
        default='',
    )
    description_en = models.TextField(
        # Translators: Provider description
        _("description in English"),
        default='',
        blank=True,
    )
    description_ar = models.TextField(
        # Translators: Provider description
        _("description in Arabic"),
        default='',
        blank=True,
    )
    description_fr = models.TextField(
        # Translators: Provider description
        _("description in French"),
        default='',
        blank=True,
    )
    user = models.OneToOneField(
        to=settings.AUTH_USER_MODEL,
        verbose_name=_('user'),
        help_text=_('user account for this provider'),
    )
    number_of_monthly_beneficiaries = models.IntegerField(
        _("number of targeted beneficiaries monthly"),
        blank=True,
        null=True,
        validators=[MinValueValidator(0),
                    MaxValueValidator(1000000)])
    focal_point_name_en = models.CharField(
        _("focal point name in English"),
        max_length=256,  # Length is a guess
        default='',
        blank=True,
        validators=[blank_or_at_least_one_letter])
    focal_point_name_ar = models.CharField(
        _("focal point name in Arabic"),
        max_length=256,  # Length is a guess
        default='',
        blank=True,
        validators=[blank_or_at_least_one_letter])
    focal_point_name_fr = models.CharField(
        _("focal point name in French"),
        max_length=256,  # Length is a guess
        default='',
        blank=True,
        validators=[blank_or_at_least_one_letter])
    focal_point_phone_number = models.CharField(
        _("focal point phone number"),
        max_length=20,
        validators=[RegexValidator(settings.PHONE_NUMBER_REGEX)])
    address_en = models.TextField(
        _("provider address in English"),
        default='',
        blank=True,
    )
    address_ar = models.TextField(
        _("provider address in Arabic"),
        default='',
        blank=True,
    )
    address_fr = models.TextField(
        _("provider address in French"),
        default='',
        blank=True,
    )

    def get_api_url(self):
        """Return the PATH part of the URL to access this object using the API"""
        return reverse('provider-detail', args=[self.id])

    def get_fetch_url(self):
        """Return the PATH part of the URL to fetch this object using the API"""
        return reverse('provider-fetch', args=[self.id])

    def notify_jira_of_change(self):
        JiraUpdateRecord.objects.create(
            update_type=JiraUpdateRecord.PROVIDER_CHANGE, provider=self)

    def get_admin_edit_url(self):
        """Return the PATH part of the URL to edit this object in the admin"""
        return reverse('admin:services_provider_change', args=[self.id])