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')
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()
class CircularAreaGeom(models.Model): geomValue = models.TextField()
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 }
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
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)
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
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'
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'
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']
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')
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
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)
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)
class Vendor(models.Model): name = models.CharField(max_length=256, unique=True) details = models.TextField(blank=True) extra_data = JSONField(null=True)
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"
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
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"
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)
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)
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"]
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)
class Attribution(models.Model): attribution = models.TextField()
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'), )
class ModelWithDataBlob(models.Model): data = models.TextField(default='{}') class Meta: abstract = True
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"
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)
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", )
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()
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])