class Models(models.Model): id = models.IntegerField(primary_key=True) # AutoField? name = models.TextField(unique=True, blank=True) lstar = models.FloatField(blank=True, null=True) mdot = models.FloatField(blank=True, null=True) tstar = models.FloatField(blank=True, null=True) teff = models.FloatField(blank=True, null=True) logg = models.FloatField(blank=True, null=True) vel_law = models.IntegerField(blank=True, null=True) vinf = models.IntegerField(blank=True, null=True) cl_par_1 = models.FloatField(blank=True, null=True) cl_par_2 = models.FloatField(blank=True, null=True) hyd_mass_frac = models.FloatField(blank=True, null=True) hyd_rel_frac = models.FloatField(blank=True, null=True) carb_rel_frac = models.FloatField(blank=True, null=True) nit_rel_frac = models.FloatField(blank=True, null=True) oxy_rel_frac = models.FloatField(blank=True, null=True) iron_rel_frac = models.FloatField(blank=True, null=True) ions = models.TextField(blank=True) # This field type is a guess. params = DictionaryField() vadat = DictionaryField() type = models.TextField(blank=True) class Meta: managed = False db_table = 'models'
class MeteorsNightsProcessed(models.Model): night = models.TextField(primary_key=True) comments = models.TextField(blank=True, null=True) params = DictionaryField() class Meta: db_table = 'meteors_nights_processed' app_label = 'favor2'
class BeholderStatus(models.Model): id = models.IntegerField(primary_key=True) time = models.DateTimeField(null=True, blank=True) status = DictionaryField() class Meta: db_table = 'beholder_status' app_label = 'favor2'
class RealtimeImages(models.Model): id = models.IntegerField(primary_key=True) object = models.ForeignKey('RealtimeObjects') record = models.ForeignKey('RealtimeRecords', null=True, blank=True) filename = models.TextField() time = models.DateTimeField(null=True, blank=True) keywords = DictionaryField() class Meta: db_table = 'realtime_images' app_label = 'favor2'
class RealtimeObjects(models.Model): id = models.IntegerField(primary_key=True) channel_id = models.IntegerField(null=True, blank=True) night = models.TextField(blank=True) time_start = models.DateTimeField(null=True, blank=True) time_end = models.DateTimeField(null=True, blank=True) state = models.ForeignKey('RealtimeObjectState', db_column='state') ra0 = models.FloatField(null=True, blank=True) dec0 = models.FloatField(null=True, blank=True) params = DictionaryField() class Meta: db_table = 'realtime_objects' app_label = 'favor2'
class Images(models.Model): id = models.IntegerField(primary_key=True) channel_id = models.IntegerField(null=True, blank=True) filter = models.ForeignKey(Filters, null=True, db_column='filter', blank=True) night = models.TextField(blank=True) filename = models.TextField(blank=True) time = models.DateTimeField(null=True, blank=True) type = models.TextField(blank=True) ra0 = models.FloatField(null=True, blank=True) dec0 = models.FloatField(null=True, blank=True) keywords = DictionaryField() class Meta: db_table = u'images' app_label = 'favor2'
class SatellitesView(models.Model): id = models.IntegerField(primary_key=True) catalogue = models.IntegerField(blank=True, null=True) catalogue_id = models.IntegerField(blank=True, null=True) name = models.TextField(blank=True) iname = models.TextField(blank=True) country = models.TextField(blank=True, null=True) type = models.IntegerField(blank=True, null=True) launch_date = models.DateTimeField(blank=True, null=True) variability = models.IntegerField(blank=True, null=True) variability_period = models.FloatField(blank=True, null=True) orbit_inclination = models.FloatField(blank=True, null=True) orbit_period = models.FloatField(blank=True, null=True) orbit_eccentricity = models.FloatField(blank=True, null=True) rcs = models.FloatField(blank=True, null=True) comments = models.TextField(blank=True) params = DictionaryField() ntracks = models.BigIntegerField(blank=True, null=True) nrecords = models.BigIntegerField(blank=True, null=True) penumbra = models.NullBooleanField() mean_clear = models.FloatField(blank=True, null=True) mean_b = models.FloatField(blank=True, null=True) mean_v = models.FloatField(blank=True, null=True) mean_r = models.FloatField(blank=True, null=True) sigma_clear = models.FloatField(blank=True, null=True) sigma_b = models.FloatField(blank=True, null=True) sigma_v = models.FloatField(blank=True, null=True) sigma_r = models.FloatField(blank=True, null=True) median_clear = models.FloatField(blank=True, null=True) median_b = models.FloatField(blank=True, null=True) median_v = models.FloatField(blank=True, null=True) median_r = models.FloatField(blank=True, null=True) min_clear = models.FloatField(blank=True, null=True) min_b = models.FloatField(blank=True, null=True) min_v = models.FloatField(blank=True, null=True) min_r = models.FloatField(blank=True, null=True) max_clear = models.FloatField(blank=True, null=True) max_b = models.FloatField(blank=True, null=True) max_v = models.FloatField(blank=True, null=True) max_r = models.FloatField(blank=True, null=True) time_last = models.DateTimeField(null=True, blank=True) time_first = models.DateTimeField(null=True, blank=True) class Meta: db_table = 'satellites_view' app_label = 'favor2'
class SchedulerTargets(models.Model): id = models.IntegerField(primary_key=True) external_id = models.IntegerField(null=True, blank=True) name = models.TextField(blank=True) type = models.TextField(blank=True) ra = models.FloatField(null=True, blank=True) dec = models.FloatField(null=True, blank=True) exposure = models.FloatField(null=True, blank=True) filter = models.TextField(blank=True) repeat = models.IntegerField(blank=True) status = models.ForeignKey(SchedulerTargetStatus, null=True, db_column='status', blank=True) time_created = models.DateTimeField(null=True, blank=True) time_completed = models.DateTimeField(null=True, blank=True) uuid = models.TextField(unique=True, blank=True) timetolive = models.FloatField(null=True, blank=True) params = DictionaryField() class Meta: db_table = 'scheduler_targets' app_label = 'favor2'
class Satellites(models.Model): id = models.IntegerField(primary_key=True) catalogue = models.IntegerField(blank=True, null=True) catalogue_id = models.IntegerField(blank=True, null=True) name = models.TextField(blank=True) iname = models.TextField(blank=True) country = models.TextField(blank=True) type = models.IntegerField(blank=True, null=True) launch_date = models.DateTimeField(null=True, blank=True) variability = models.IntegerField(blank=True, null=True) variability_period = models.FloatField(blank=True, null=True) orbit_inclination = models.FloatField(blank=True, null=True) orbit_period = models.FloatField(blank=True, null=True) orbit_eccentricity = models.FloatField(blank=True, null=True) rcs = models.FloatField(blank=True, null=True) comments = models.TextField(blank=True) params = DictionaryField() class Meta: db_table = 'satellites' app_label = 'favor2'
class RealtimeRecords(models.Model): id = models.IntegerField(primary_key=True) object = models.ForeignKey(RealtimeObjects, null=True, blank=True) time = models.DateTimeField(null=True, blank=True) ra = models.FloatField(null=True, blank=True) dec = models.FloatField(null=True, blank=True) x = models.FloatField(null=True, blank=True) y = models.FloatField(null=True, blank=True) a = models.FloatField(null=True, blank=True) b = models.FloatField(null=True, blank=True) theta = models.FloatField(null=True, blank=True) flux = models.FloatField(null=True, blank=True) flux_err = models.FloatField(null=True, blank=True) mag = models.FloatField(null=True, blank=True) mag_err = models.FloatField(null=True, blank=True) filter = models.ForeignKey(Filters, null=True, db_column='filter', blank=True) flags = models.IntegerField(null=True, blank=True) params = DictionaryField() class Meta: db_table = 'realtime_records' app_label = 'favor2'
class SurveyTransients(models.Model): id = models.IntegerField(primary_key=True) channel_id = models.IntegerField(blank=True, null=True) frame = models.ForeignKey(Images, blank=True, null=True) time = models.DateTimeField(blank=True, null=True) night = models.TextField(blank=True) filter = models.ForeignKey(Filters, db_column='filter', blank=True, null=True) ra = models.FloatField(blank=True, null=True) dec = models.FloatField(blank=True, null=True) mag = models.FloatField(blank=True, null=True) mag_err = models.FloatField(blank=True, null=True) flux = models.FloatField(blank=True, null=True) flux_err = models.FloatField(blank=True, null=True) x = models.FloatField(blank=True, null=True) y = models.FloatField(blank=True, null=True) flags = models.IntegerField(blank=True, null=True) preview = models.TextField(blank=True) simbad = models.TextField(blank=True) mpc = models.TextField(blank=True) params = DictionaryField() class Meta: db_table = 'survey_transients' app_label = 'favor2'
class Instance(models.Model): """ Stores instances of multiple elastic models """ schema = models.ForeignKey(Schema, on_delete=models.CASCADE, related_name='instances') data = DictionaryField() created_at = models.DateTimeField(auto_now_add=True) updated_at = models.DateTimeField(auto_now=True) objects = hstore.HStoreManager() @classmethod def load_schema(cls, schema=None): """ Build model fields referencing hstore keys based on schema """ hstore_dictionary_field = cls._meta.get_field('data') if schema is not None: hstore_dictionary_field.reload_schema( schema.get_hstore_field_definitions()) else: hstore_dictionary_field.reload_schema(None)
class Device(BaseAccessLevel): """ Device Model Represents a network device eg: an outdoor point-to-point wifi device, a BGP router, a server, and so on """ name = models.CharField(_('name'), max_length=50) node = models.ForeignKey('nodes.Node', verbose_name=_('node')) type = models.CharField(_('type'), max_length=50, choices=DEVICE_TYPES_CHOICES) status = models.SmallIntegerField(_('status'), max_length=2, choices=DEVICE_STATUS_CHOICES, default=DEVICE_STATUS.get('unknown')) # geographic data location = models.PointField( _('location'), blank=True, null=True, help_text=_("""specify device coordinates (if different from node); defaults to node coordinates if node is a point, otherwise if node is a geometry it will default to che centroid of the geometry""" )) elev = models.FloatField(_('elevation'), blank=True, null=True) # device specific routing_protocols = models.ManyToManyField('net.RoutingProtocol', blank=True) os = models.CharField(_('operating system'), max_length=128, blank=True, null=True) os_version = models.CharField(_('operating system version'), max_length=128, blank=True, null=True) first_seen = models.DateTimeField(_('first time seen on'), blank=True, null=True, default=None) last_seen = models.DateTimeField(_('last time seen on'), blank=True, null=True, default=None) # text description = models.CharField(_('description'), max_length=255, blank=True, null=True) notes = models.TextField(_('notes'), blank=True, null=True) # extra data data = DictionaryField( _('extra data'), null=True, blank=True, help_text=_('store extra attributes in JSON string')) shortcuts = ReferencesField(null=True, blank=True) objects = DeviceManager() # list indicating if any other module has extended this model extended_by = [] class Meta: app_label = 'net' def __unicode__(self): return '%s' % self.name def save(self, *args, **kwargs): """ Custom save method does the following: * automatically inherit node coordinates and elevation * save shortcuts if HSTORE is enabled """ custom_checks = kwargs.pop('custom_checks', True) super(Device, self).save(*args, **kwargs) if custom_checks is False: return changed = False if not self.location: self.location = self.node.point changed = True if not self.elev and self.node.elev: self.elev = self.node.elev changed = True original_user = self.shortcuts.get('user') if self.node.user: self.shortcuts['user'] = self.node.user if original_user != self.shortcuts.get('user'): changed = True if 'nodeshot.core.layers' in settings.INSTALLED_APPS: original_layer = self.shortcuts.get('layer') self.shortcuts['layer'] = self.node.layer if original_layer != self.shortcuts.get('layer'): changed = True if changed: self.save(custom_checks=False) @property def owner(self): if 'user' not in self.shortcuts: if self.node or self.node_id: self.save() else: raise Exception('Instance does not have a node set yet') return self.shortcuts['user'] @property def layer(self): if 'nodeshot.core.layers' not in settings.INSTALLED_APPS: return False if 'layer' not in self.shortcuts: if self.node or self.node_id: self.save() else: raise Exception('Instance does not have a node set yet') return self.shortcuts['layer'] if 'grappelli' in settings.INSTALLED_APPS: @staticmethod def autocomplete_search_fields(): return ('name__icontains', )
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(class_, 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 class_._additional_validation.append(method_name) # add method to this class setattr(class_, 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 Interface(BaseAccessLevel): """ Interface model """ device = models.ForeignKey('net.Device') type = models.IntegerField(_('type'), max_length=2, choices=INTERFACE_TYPE_CHOICES, blank=True) name = models.CharField(_('name'), max_length=10, blank=True, null=True) mac = MACAddressField(_('mac address'), max_length=17, unique=True, default=None, null=True, blank=True) mtu = models.IntegerField(_('MTU'), blank=True, null=True, default=1500, help_text=_('Maximum Trasmission Unit')) tx_rate = models.IntegerField(_('TX Rate'), null=True, default=None, blank=True) rx_rate = models.IntegerField(_('RX Rate'), null=True, default=None, blank=True) # extra data data = DictionaryField( _('extra data'), null=True, blank=True, help_text=_('store extra attributes in JSON string')) shortcuts = ReferencesField(null=True, blank=True) objects = InterfaceManager() class Meta: app_label = 'net' def __unicode__(self): return '%s %s' % (self.get_type_display(), self.mac) def save(self, *args, **kwargs): """ Custom save method does the following: * save shortcuts if HSTORE is enabled """ if 'node' not in self.shortcuts: self.shortcuts['node'] = self.device.node if 'user' not in self.shortcuts and self.device.node.user: self.shortcuts['user'] = self.device.node.user if 'layer' not in self.shortcuts and 'nodeshot.core.layers' in settings.INSTALLED_APPS: self.shortcuts['layer'] = self.device.node.layer super(Interface, self).save(*args, **kwargs) @property def owner(self): if 'user' not in self.shortcuts: if self.device or self.device_id: self.save() else: raise Exception('Instance does not have a device set yet') return self.shortcuts['user'] @property def node(self): if 'node' not in self.shortcuts: if self.device or self.device_id: self.save() else: raise Exception('Instance does not have a device set yet') return self.shortcuts['node'] @property def layer(self): if 'nodeshot.core.layers' not in settings.INSTALLED_APPS: return False if 'layer' not in self.shortcuts: if self.device or self.device_id: self.save() else: raise Exception('Instance does not have a device set yet') return self.shortcuts['layer'] @property def ip_addresses(self): try: addresses = self.data.get('ip_addresses', '') # self.data might be none, hence self.data['ip_addresses'] will raise an exception except AttributeError: addresses = '' return addresses.replace(' ', '').split(',') if addresses else [] @ip_addresses.setter def ip_addresses(self, value): """ :param value: a list of ip addresses """ if not isinstance(value, list): raise ValueError('ip_addresses value must be a list') # in soem cases self.data might be none, so let's instantiate an empty dict if self.data is None: self.data = {} # update field self.data['ip_addresses'] = ', '.join(value) if 'grappelli' in settings.INSTALLED_APPS: @staticmethod def autocomplete_search_fields(): return ('mac__icontains', 'data__icontains')
class Layer(BaseDate): """ Layer Model """ name = models.CharField(_('name'), max_length=50, unique=True) slug = models.SlugField(max_length=50, db_index=True, unique=True) description = models.CharField(_('description'), max_length=250, blank=True, null=True, help_text=_('short description of this layer')) text = models.TextField(_('extended text'), blank=True, null=True, help_text=_('extended description, specific instructions, links, ecc.')) # record management is_published = models.BooleanField(_('published'), default=True) is_external = models.BooleanField(_('is it external?'), default=False) # geographic related fields center = models.PointField(_('center coordinates'), null=True, blank=True) area = models.PolygonField(_('area'), null=True, blank=True) zoom = models.SmallIntegerField(_('default zoom level'), choices=MAP_ZOOM_CHOICES, default=ZOOM_DEFAULT) # organizational organization = models.CharField(_('organization'), help_text=_('Organization which is responsible to manage this layer'), max_length=255) website = models.URLField(_('Website'), blank=True, null=True) email = models.EmailField(_('email'), help_text=_("""possibly an email address that delivers messages to all the active participants; if you don't have such an email you can add specific users in the "mantainers" field"""), blank=True) mantainers = models.ManyToManyField(settings.AUTH_USER_MODEL, verbose_name=_('mantainers'), help_text=_('you can specify the users who are mantaining this layer so they will receive emails from the system'), blank=True) # settings # TODO: rename minimum_distance to nodes_minimum_distance minimum_distance = models.IntegerField(default=NODE_MINIMUM_DISTANCE, help_text=_('minimum distance between nodes in meters, 0 means feature disabled')) new_nodes_allowed = models.BooleanField(_('new nodes allowed'), default=True, help_text=_('indicates whether users can add new nodes to this layer')) # TODO: HSTORE_SCHEMA setting data = DictionaryField(_('extra data'), null=True, blank=True,\ help_text=_('store extra attributes in JSON string')) # default manager objects = LayerManager() # this is needed to check if the is_published is changing # explained here: # http://stackoverflow.com/questions/1355150/django-when-saving-how-can-you-check-if-a-field-has-changed _current_is_published = None class Meta: db_table = 'layers_layer' app_label= 'layers' def __unicode__(self): return '%s' % self.name def __init__(self, *args, **kwargs): """ Fill __current_is_published """ super(Layer, self).__init__(*args, **kwargs) # set current is_published, but only if it is an existing layer if self.pk: self._current_is_published = self.is_published def save(self, *args, **kwargs): """ intercepts changes to is_published and fires layer_is_published_changed signal """ super(Layer, self).save(*args, **kwargs) # if is_published of an existing layer changes if self.pk and self.is_published != self._current_is_published: # send django signal layer_is_published_changed.send( sender=self.__class__, instance=self, old_is_published=self._current_is_published, new_is_published=self.is_published ) # unpublish nodes self.update_nodes_published() # update _current_is_published self._current_is_published = self.is_published def update_nodes_published(self): """ publish or unpublish nodes of current layer """ if self.pk: self.node_set.all().update(is_published=self.is_published) if 'grappelli' in settings.INSTALLED_APPS: @staticmethod def autocomplete_search_fields(): return ('name__icontains', 'slug__icontains')
class Layer(BaseDate): """ Layer Model A layer represent a categorization of nodes. Layers might have geographical boundaries and might be managed by certain organizations. """ name = models.CharField(_('name'), max_length=50, unique=True) slug = models.SlugField(max_length=50, db_index=True, unique=True) description = models.CharField( _('description'), max_length=250, blank=True, null=True, help_text=_('short description of this layer')) text = models.TextField( _('extended text'), blank=True, null=True, help_text=_( 'extended description, specific instructions, links, ecc.')) # record management is_published = models.BooleanField(_('published'), default=True) is_external = models.BooleanField(_('is it external?'), default=False) # geographic related fields area = models.GeometryField( _('area'), help_text= _('If a polygon is used nodes of this layer will have to be contained in it.\ If a point is used nodes of this layer can be located anywhere. Lines are not allowed.' )) # organizational organization = models.CharField( _('organization'), max_length=255, blank=True, help_text=_('Organization which is responsible to manage this layer')) website = models.URLField(_('Website'), blank=True, null=True) email = models.EmailField( _('email'), blank=True, help_text= _("""possibly an email address that delivers messages to all the active participants; if you don't have such an email you can add specific users in the "mantainers" field""" )) mantainers = models.ManyToManyField( settings.AUTH_USER_MODEL, verbose_name=_('mantainers'), blank=True, help_text= _('you can specify the users who are mantaining this layer so they will receive emails from the system' )) # settings nodes_minimum_distance = models.IntegerField( default=NODES_MINIMUM_DISTANCE, help_text= _('minimum distance between nodes in meters, 0 means there is no minimum distance' )) new_nodes_allowed = models.BooleanField( _('new nodes allowed'), default=True, help_text=_('indicates whether users can add new nodes to this layer')) data = DictionaryField(_('extra data'), schema=HSTORE_SCHEMA, null=True, editable=False) # default manager objects = LayerManager() # this is needed to check if the is_published is changing # explained here: # http://stackoverflow.com/questions/1355150/django-when-saving-how-can-you-check-if-a-field-has-changed _current_is_published = None class Meta: db_table = 'layers_layer' app_label = 'layers' def __unicode__(self): return self.name def __init__(self, *args, **kwargs): """ Fill _current_is_published """ super(Layer, self).__init__(*args, **kwargs) # set current is_published, but only if it is an existing layer if self.pk: self._current_is_published = self.is_published def save(self, *args, **kwargs): """ intercepts changes to is_published and fires layer_is_published_changed signal """ super(Layer, self).save(*args, **kwargs) # if is_published of an existing layer changes if self.pk and self.is_published != self._current_is_published: # send django signal layer_is_published_changed.send( sender=self.__class__, instance=self, old_is_published=self._current_is_published, new_is_published=self.is_published) # unpublish nodes self.update_nodes_published() # update _current_is_published self._current_is_published = self.is_published def clean(self): """ Ensure area is either a Point or a Polygon """ if not isinstance(self.area, (Polygon, Point)): raise ValidationError('area can be only of type Polygon or Point') @property def center(self): # if area is point just return that if isinstance(self.area, Point) or self.area is None: return self.area # otherwise return point_on_surface or centroid try: # point_on_surface guarantees that the point is within the geometry return self.area.point_on_surface except GEOSException: # fall back on centroid which may not be within the geometry # for example, a horseshoe shaped polygon return self.area.centroid def update_nodes_published(self): """ publish or unpublish nodes of current layer """ if self.pk: self.node_set.all().update(is_published=self.is_published) if 'grappelli' in settings.INSTALLED_APPS: @staticmethod def autocomplete_search_fields(): return ('name__icontains', 'slug__icontains')
class Link(BaseAccessLevel): """ Link Model Designed for both wireless and wired links """ type = models.SmallIntegerField(_('type'), max_length=10, null=True, blank=True, choices=choicify(LINK_TYPES), default=LINK_TYPES.get('radio')) # in most cases these two fields are mandatory, except for "planned" links interface_a = models.ForeignKey( Interface, verbose_name=_('from interface'), related_name='link_interface_from', blank=True, null=True, help_text= _('mandatory except for "planned" links (in planned links you might not have any device installed yet)' )) interface_b = models.ForeignKey( Interface, verbose_name=_('to interface'), related_name='link_interface_to', blank=True, null=True, help_text= _('mandatory except for "planned" links (in planned links you might not have any device installed yet)' )) # in "planned" links these two fields are necessary # while in all the other status they serve as a shortcut node_a = models.ForeignKey( Node, verbose_name=_('from node'), related_name='link_node_from', blank=True, null=True, help_text= _('leave blank (except for planned nodes) as it will be filled in automatically' )) node_b = models.ForeignKey( Node, verbose_name=_('to node'), related_name='link_node_to', blank=True, null=True, help_text= _('leave blank (except for planned nodes) as it will be filled in automatically' )) # shortcut layer = models.ForeignKey( Layer, verbose_name=_('layer'), blank=True, null=True, help_text=_('leave blank - it will be filled in automatically')) # geospatial info line = models.LineStringField( blank=True, null=True, help_text=_('leave blank and the line will be drawn automatically')) # monitoring info status = models.SmallIntegerField(_('status'), choices=choicify(LINK_STATUS), default=LINK_STATUS.get('planned')) first_seen = models.DateTimeField(_('first time seen on'), blank=True, null=True, default=None) last_seen = models.DateTimeField(_('last time seen on'), blank=True, null=True, default=None) # technical info metric_type = models.CharField(_('metric type'), max_length=6, choices=choicify(METRIC_TYPES), blank=True, null=True) metric_value = models.FloatField(_('metric value'), blank=True, null=True) max_rate = models.IntegerField(_('Maximum BPS'), null=True, default=None, blank=True) min_rate = models.IntegerField(_('Minimum BPS'), null=True, default=None, blank=True) # wireless specific info dbm = models.IntegerField(_('dBm average'), null=True, default=None, blank=True) noise = models.IntegerField(_('noise average'), null=True, default=None, blank=True) # additional data data = DictionaryField( _('extra data'), null=True, blank=True, help_text=_('store extra attributes in JSON string')) shortcuts = ReferencesField(null=True, blank=True) # django manager objects = LinkManager() class Meta: app_label = 'links' def __unicode__(self): return _(u'%s <> %s') % (self.node_a_name, self.node_b_name) def clean(self, *args, **kwargs): """ Custom validation 1. interface_a and interface_b mandatory except for planned links 2. planned links should have at least node_a and node_b filled in 3. dbm and noise fields can be filled only for radio links 4. interface_a and interface_b must differ 5. interface a and b type must match """ if self.status != LINK_STATUS.get('planned') and ( self.interface_a is None or self.interface_b is None): raise ValidationError( _('fields "from interface" and "to interface" are mandatory in this case' )) if self.status == LINK_STATUS.get('planned') and ( self.node_a is None or self.node_b is None): raise ValidationError( _('fields "from node" and "to node" are mandatory for planned links' )) if self.type != LINK_TYPES.get('radio') and (self.dbm is not None or self.noise is not None): raise ValidationError( _('Only links of type "radio" can contain "dbm" and "noise" information' )) if (self.interface_a_id == self.interface_b_id) or (self.interface_a == self.interface_b): raise ValidationError( _('link cannot have same "from interface" and "to interface"')) if (self.interface_a and self.interface_b ) and self.interface_a.type != self.interface_b.type: format_tuple = (self.interface_a.get_type_display(), self.interface_b.get_type_display()) raise ValidationError( _('link cannot be between of interfaces of different types:\ interface a is "%s" while b is "%s"') % format_tuple) def save(self, *args, **kwargs): """ Custom save does the following: * determine link type if not specified * automatically fill 'node_a' and 'node_b' fields if necessary * draw line between two nodes * fill shortcut properties node_a_name and node_b_name """ if not self.type: if self.interface_a.type == INTERFACE_TYPES.get('wireless'): self.type = LINK_TYPES.get('radio') elif self.interface_a.type == INTERFACE_TYPES.get('ethernet'): self.type = LINK_TYPES.get('ethernet') else: self.type = LINK_TYPES.get('virtual') if self.interface_a_id: self.interface_a = Interface.objects.get(pk=self.interface_a_id) if self.interface_b_id: self.interface_b = Interface.objects.get(pk=self.interface_b_id) # fill in node_a and node_b if self.node_a is None and self.interface_a is not None: self.node_a = self.interface_a.node if self.node_b is None and self.interface_b is not None: self.node_b = self.interface_b.node # fill layer from node_a if self.layer is None: self.layer = self.node_a.layer # draw linestring if not self.line: self.line = LineString(self.node_a.point, self.node_b.point) # fill properties if self.data is None or self.data.get('node_a_name', None) is None: self.data = self.data or {} # in case is None init empty dict self.data['node_a_name'] = self.node_a.name self.data['node_b_name'] = self.node_b.name if self.data.get('node_a_slug', None) is None or self.data.get( 'node_b_slug', None) is None: self.data['node_a_slug'] = self.node_a.slug self.data['node_b_slug'] = self.node_b.slug if self.data.get('interface_a_mac', None) is None or self.data.get( 'interface_b_mac', None) is None: self.data['interface_a_mac'] = self.interface_a.mac self.data['interface_b_mac'] = self.interface_b.mac if self.data.get('layer_slug') != self.layer.slug: self.data['layer_slug'] = self.layer.slug super(Link, self).save(*args, **kwargs) @property def node_a_name(self): self.data = self.data or {} return self.data.get('node_a_name', None) @property def node_b_name(self): self.data = self.data or {} return self.data.get('node_b_name', None) @property def node_a_slug(self): self.data = self.data or {} return self.data.get('node_a_slug', None) @property def node_b_slug(self): self.data = self.data or {} return self.data.get('node_b_slug', None) @property def interface_a_mac(self): self.data = self.data or {} return self.data.get('interface_a_mac', None) @property def interface_b_mac(self): self.data = self.data or {} return self.data.get('interface_b_mac', None) @property def layer_slug(self): self.data = self.data or {} return self.data.get('layer_slug', None) @property def quality(self): """ Quality is a number between 1 and 6 that rates the quality of the link. The way quality is calculated might be overridden by settings. 0 means unknown """ if self.metric_value is None: return 0 # PLACEHOLDER return 6
class DeviceConnector(BaseDate, BaseOrdered): """ DeviceConnector Model """ backend = models.CharField( _('backend'), max_length=128, choices=settings.NODESHOT['NETENGINE_BACKENDS'], help_text= _('select the operating system / protocol to use to retrieve info from device' )) node = models.ForeignKey('nodes.Node', verbose_name=_('node')) host = models.CharField(_('host'), max_length=128) config = DictionaryField( _('config'), blank=True, null=True, help_text= _('backend specific parameters, eg: username/password (SSH), community (SNMP)' )) port = models.IntegerField( _('port'), blank=True, null=True, help_text=_( 'leave blank to use the default port for the protocol in use')) store = models.BooleanField( _('store in DB?'), default=True, help_text=_('is adviced to store read-only credentials only')) device = models.ForeignKey( Device, verbose_name=_('device'), blank=True, null=True, help_text=_('leave blank, will be created automatically')) # django manager objects = HStoreNodeshotManager() __netengine = None __backend_class = None class Meta: ordering = ["order"] app_label = 'connectors' verbose_name = _('device connector') verbose_name_plural = _('device connectors') def __unicode__(self): if self.host: return self.host else: return _(u'Unsaved Device Connector') def save(self, *args, **kwargs): """ Custom save does the following: * strip trailing whitespace from host attribute * create device and all other related objects * store connection config in DB if store attribute is True """ self.host = self.host.strip() if not self.id: self.device = self.__create_device() if self.store is True: super(DeviceConnector, self).save(*args, **kwargs) def clean(self, *args, **kwargs): """ validation """ self._validate_backend() self._validate_config() self._validate_netengine() self._validate_duplicates() @property def REQUIRED_CONFIG_KEYS(self): return self._get_netengine_arguments(required=True) @property def AVAILABLE_CONFIG_KEYS(self): return self._get_netengine_arguments() @property def backend_class(self): """ returns python netengine backend class, importing it if needed """ if not self.backend: return None if not self.__backend_class: self.__backend_class = self._get_netengine_backend() return self.__backend_class @property def netengine(self): """ access netengine instance """ # return None if no backend chosen yet if not self.backend: return None # init instance of the netengine backend if not already done if not self.__netengine: NetengineBackend = self.backend_class arguments = self._build_netengine_arguments() self.__netengine = NetengineBackend(**arguments) # return netengine instance return self.__netengine def _validate_backend(self): """ ensure backend string representation is correct """ try: self.backend_class # if we get an import error the specified path is wrong except (ImportError, AttributeError) as e: raise ValidationError( _('No valid backend found, got the following python exception: "%s"' ) % e) def _validate_config(self): """ ensure REQUIRED_CONFIG_KEYS are filled """ # exit if no backend specified if not self.backend: return # exit if no required config keys if len(self.REQUIRED_CONFIG_KEYS) < 1: return self.config = self.config or {} # default to empty dict of no config required_keys_set = set(self.REQUIRED_CONFIG_KEYS) config_keys_set = set(self.config.keys()) missing_required_keys = required_keys_set - config_keys_set unrecognized_keys = config_keys_set - required_keys_set # if any missing required key raise ValidationError if len(missing_required_keys) > 0: # converts list in comma separated string missing_keys_string = ', '.join(missing_required_keys) # django error raise ValidationError( _('Missing required config keys: "%s"') % missing_keys_string) elif len(unrecognized_keys) > 0: # converts list in comma separated string unrecognized_keys_string = ', '.join(unrecognized_keys) # django error raise ValidationError( _('Unrecognized config keys: "%s"') % unrecognized_keys_string) def _validate_netengine(self): """ call netengine validate() method verifies connection parameters are correct """ if self.backend: try: self.netengine.validate() except NetEngineError as e: raise ValidationError(e) def _validate_duplicates(self): """ Ensure we're not creating a device that already exists Runs only when the DeviceConnector object is created, not when is updated """ # if connector is being created right now if not self.id: duplicates = [] self.netengine_dict = self.netengine.to_dict() # loop over interfaces and check mac address for interface in self.netengine_dict['interfaces']: # avoid checking twice for the same interface (often ifconfig returns duplicates) if interface['mac_address'] in duplicates: continue # check in DB if Interface.objects.filter( mac__iexact=interface['mac_address']).count() > 0: duplicates.append(interface['mac_address']) # if we have duplicates raise validation error if len(duplicates) > 0: mac_address_string = ', '.join(duplicates) raise ValidationError( _('interfaces with the following mac addresses already exist: %s' ) % mac_address_string) def _get_netengine_arguments(self, required=False): """ returns list of available config params returns list of required config params if required is True for internal use only """ # inspect netengine class backend_class = self._get_netengine_backend() argspec = inspect.getargspec(backend_class.__init__) # store args args = argspec.args # remove known arguments for argument_name in ['self', 'host', 'port']: args.remove(argument_name) if required: # list of default values default_values = list(argspec.defaults) # always remove last default value, which is port number default_values = default_values[0:-1] # remove an amount of arguments equals to number of default values, starting from right args = args[0:len(args) - len(default_values)] return args def _get_netengine_backend(self): """ returns the netengine backend specified in self.backend for internal use only """ # extract backend class name, eg: AirOS or OpenWRT backend_class_name = self.backend.split('.')[-1] # convert to lowercase to get the path backend_path = self.backend.lower() # import module by its path module = import_module(backend_path) # get netengine backend class BackendClass = getattr(module, backend_class_name) return BackendClass def _build_netengine_arguments(self): """ returns a python dictionary representing arguments that will be passed to a netengine backend for internal use only """ arguments = {"host": self.host} if self.config is not None: for key, value in self.config.iteritems(): arguments[key] = value if self.port: arguments["port"] = self.port return arguments def get_auto_order_queryset(self): """ Overriding a method of BaseOrdered Abstract Model """ return self.__class__.objects.filter(device=self.device) def __create_device(self): """ creates device, internal use only """ # retrieve netengine dictionary from memory or from network device_dict = getattr(self, 'netengine_dict', self.netengine.to_dict()) device = Device() device.node_id = self.node_id device.name = device_dict['name'] device.type = device_dict['type'] device.status = DEVICE_STATUS.get('reachable') device.os = device_dict['os'] device.os_version = device_dict['os_version'] # this is the first time the device is seen by the system because we are just adding it device.first_seen = now() # and is also the latest device.last_seen = now() device.full_clean() device.save() # add routing protocols for routing_protocol in device_dict['routing_protocols']: # retrieve routing protocol from DB try: rp = RoutingProtocol.objects.filter( name__iexact=routing_protocol['name'], version__iexact=routing_protocol['version'])[0] # create if doesn't exist yet except IndexError: rp = RoutingProtocol(name=routing_protocol['name'], version=routing_protocol['version']) rp.full_clean() rp.save() # add to device device.routing_protocols.add(rp) for interface in device_dict['interfaces']: interface_object = False vap_object = False # create interface depending on type if interface['type'] == 'ethernet': interface_object = Ethernet( **{ 'device': device, 'name': interface['name'], 'mac': interface['mac_address'], 'mtu': interface['mtu'], 'standard': interface['standard'], 'duplex': interface['duplex'], 'tx_rate': interface['tx_rate'], 'rx_rate': interface['rx_rate'] }) elif interface['type'] == 'wireless': interface_object = Wireless( **{ 'device': device, 'name': interface['name'], 'mac': interface['mac_address'], 'mtu': interface['mtu'], 'mode': interface['mode'], 'standard': interface['standard'], 'channel': interface['channel'], 'channel_width': interface['channel_width'], 'output_power': interface['output_power'], 'dbm': interface['dbm'], 'noise': interface['noise'], 'tx_rate': interface['tx_rate'], 'rx_rate': interface['rx_rate'] }) for vap in interface['vap']: vap_object = Vap(essid=vap['essid'], bssid=vap['bssid'], encryption=vap['encryption']) if interface_object: interface_object.full_clean() interface_object.save() if vap_object: vap_object.interface = interface_object vap_object.full_clean() vap_object.save() for ip in interface['ip']: ip_object = Ip(**{ 'interface': interface_object, 'address': ip['address'], }) ip_object.full_clean() ip_object.save() if HARDWARE_INSTALLED: # try getting device model from db try: device_model = DeviceModel.objects.filter( name__iexact=device_dict['model'])[0] # if it does not exist create it except IndexError as e: # try getting manufacturer from DB try: manufacturer = Manufacturer.objects.filter( name__iexact=device_dict['manufacturer'])[0] # or create except IndexError as e: manufacturer = Manufacturer( name=device_dict['manufacturer']) manufacturer.full_clean() manufacturer.save() device_model = DeviceModel(manufacturer=manufacturer, name=device_dict['model']) device_model.ram = device_dict['RAM_total'] device_model.full_clean() device_model.save() # create relation between device model and device rel = DeviceToModelRel(device=device, model=device_model) rel.full_clean() rel.save() return device
class LayerExternal(models.Model): """ External Layers, extend 'Layers' with additional files These are the layers that are managed by local groups or other organizations """ layer = models.OneToOneField('layers.Layer', verbose_name=_('layer'), parent_link=True, related_name='external') synchronizer_path = models.CharField(_('synchronizer'), max_length=128, choices=SYNCHRONIZERS, default='None') config = DictionaryField(_('configuration'), help_text=_('Synchronizer specific configuration (eg: API URL, auth info, ecc)'), blank=True, null=True, editable=False, schema=None) # private attributes that will hold synchronizer info _synchronizer = None _synchronizer_class = None class Meta: app_label = 'sync' db_table = 'layers_external' verbose_name = _('external layer') verbose_name_plural = _('external layer info') def __unicode__(self): try: return '%s external layer config' % self.layer.name except ObjectDoesNotExist: return 'LayerExternal object' def __init__(self, *args, **kwargs): """ custom init method """ super(LayerExternal, self).__init__(*args, **kwargs) # avoid blocking page loading in case of missing required config keys in configuration try: synchronizer = self.synchronizer except ImproperlyConfigured: synchronizer = False # if synchronizer has get_nodes method # add get_nodes method to current LayerExternal instance if synchronizer is not False and hasattr(synchronizer, 'get_nodes'): self.get_nodes = synchronizer.get_nodes # load schema self._reload_schema() def _reload_schema(self): if self.synchronizer_class: schema = self.synchronizer_class.SCHEMA else: schema = None if self.config.field.schema is not schema: self.config.field.reload_schema(schema) # if schema is None set editable to False if schema is None: self.config.field.editable = False def clean(self, *args, **kwargs): """ Call self.synchronizer.clean method """ if self.synchronizer_path != 'None' and self.config: # call synchronizer custom clean try: self.synchronizer.load_config(self.config) self.synchronizer.clean() except ImproperlyConfigured as e: raise ValidationError(e.message) def save(self, *args, **kwargs): """ call synchronizer "after_external_layer_saved" method for any additional operation that must be executed after save """ after_save = kwargs.pop('after_save', True) super(LayerExternal, self).save(*args, **kwargs) # call after_external_layer_saved method of synchronizer if after_save: try: synchronizer = self.synchronizer except ImproperlyConfigured: pass else: if synchronizer: synchronizer.after_external_layer_saved(self.config) # reload schema self._reload_schema() @property def synchronizer(self): """ access synchronizer """ if not self.synchronizer_path or self.synchronizer_path == 'None' or not self.layer: return False # ensure data is up to date if (self._synchronizer is not None and self._synchronizer_class.__name__ not in self.synchronizer_path): self._synchronizer = None self._synchronizer_class = None # init synchronizer only if necessary if not self._synchronizer: self._synchronizer = (self.synchronizer_class)(self.layer) return self._synchronizer @property def synchronizer_class(self): """ returns synchronizer class """ if not self.synchronizer_path or self.synchronizer_path == 'None' or not self.layer: return False # ensure data is up to date if (self._synchronizer_class is not None and self._synchronizer_class.__name__ not in self.synchronizer_path): self._synchronizer = None self._synchronizer_class = None # import synchronizer class only if not imported already if not self._synchronizer_class: self._synchronizer_class = import_by_path(self.synchronizer_path) return self._synchronizer_class
class Layer(BaseDate): """ Layer Model """ name = models.CharField(_('name'), max_length=50, unique=True) slug = models.SlugField(max_length=50, db_index=True, unique=True) description = models.CharField( _('description'), max_length=250, blank=True, null=True, help_text=_('short description of this layer')) text = models.TextField( _('extended text'), blank=True, null=True, help_text=_( 'extended description, specific instructions, links, ecc.')) # record management is_published = models.BooleanField(_('published'), default=True) is_external = models.BooleanField(_('is it external?')) # geographic related fields center = models.PointField(_('center coordinates'), null=True, blank=True) area = models.PolygonField(_('area'), null=True, blank=True) zoom = models.SmallIntegerField( _('default zoom level'), choices=MAP_ZOOM, default=settings.NODESHOT['DEFAULTS']['LAYER_ZOOM']) # organizational organization = models.CharField( _('organization'), help_text=_('Organization which is responsible to manage this layer'), max_length=255) website = models.URLField(_('Website'), blank=True, null=True) email = models.EmailField( _('email'), help_text= _("""possibly an email address that delivers messages to all the active participants; if you don't have such an email you can add specific users in the "mantainers" field""" ), blank=True) mantainers = models.ManyToManyField( settings.AUTH_USER_MODEL, verbose_name=_('mantainers'), help_text= _('you can specify the users who are mantaining this layer so they will receive emails from the system' ), blank=True) # settings minimum_distance = models.IntegerField( default=settings.NODESHOT['DEFAULTS']['LAYER_MINIMUM_DISTANCE'], help_text=_( 'minimum distance between nodes in meters, 0 means feature disabled' )) new_nodes_allowed = models.BooleanField( _('new nodes allowed'), default=True, help_text=_('indicates whether users can add new nodes to this layer')) if HSTORE_ENABLED: data = DictionaryField( _('extra data'), null=True, blank=True, help_text=_('store extra attributes in JSON string')) # default manager objects = LayerManager() class Meta: db_table = 'layers_layer' app_label = 'layers' def __unicode__(self): return '%s' % self.name if 'grappelli' in settings.INSTALLED_APPS: @staticmethod def autocomplete_search_fields(): return ('name__icontains', 'slug__icontains')
class UserDefinedCollectionValue(UserTrackable, models.Model): """ UserDefinedCollectionValue does not inherit either the authorizable or auditable traits, however it does participate in those systems. In particular, the authorization for a collection UDF is based on the udf name and model. So if there is a collection udf called 'Stewardship' on 'Plot' then the only field permission that matters is 'Plot'/'udf:Stewardship' Each UserDefinedCollectionValue represents a new entry in a particular collection field. We audit all of the fields on this object and expand the audits in the same way that scalar udfs work. """ field_definition = models.ForeignKey('UserDefinedFieldDefinition') model_id = models.IntegerField() data = DictionaryField() objects = HStoreManager() def __unicode__(self): return repr(self.data) def __init__(self, *args, **kwargs): super(UserDefinedCollectionValue, self).__init__(*args, **kwargs) self._do_not_track.add('data') self.populate_previous_state() @property def tracked_fields(self): return super(UserDefinedCollectionValue, self).tracked_fields + \ ['udf:' + name for name in self.udf_field_names] def validate_foreign_keys_exist(self): """ This is used to check if a given foreign key exists as part of the audit system. However, this is no foreign key coupling to other auditable/pending models, so we can skip this validation step """ pass @staticmethod def get_display_model_name(audit_name, instance=None): if audit_name.startswith('udf:'): try: # UDF Collections store their model names in the audit table as # udf:<pk of UserDefinedFieldDefinition> pk = int(audit_name[4:]) if not instance: udf_def = UserDefinedFieldDefinition.objects.get(pk=pk) return udf_def.name else: for udf_def in udf_defs(instance): if udf_def.pk == pk: return udf_def.name except (ValueError, UserDefinedFieldDefinition.DoesNotExist): pass # If something goes wrong, just use the defaults return audit_name @classmethod def action_format_string_for_audit(cls, audit): if audit.field == 'id' or audit.field is None: lang = { Audit.Type.Insert: trans('created a %(model)s entry'), Audit.Type.Update: trans('updated the %(model)s entry'), Audit.Type.Delete: trans('deleted the %(model)s entry'), Audit.Type.PendingApprove: trans('approved an edit ' 'to the %(model)s entry'), Audit.Type.PendingReject: trans('rejected an ' 'edit to the %(model)s entry') } return lang[audit.action] return Auditable.action_format_string_for_audit(audit) @classmethod def short_descr(cls, audit): # model_id and field_definition aren't very useful changes to see if audit.field in {'model_id', 'field_definition'}: return None format_string = cls.action_format_string_for_audit(audit) model_name = audit.model field = audit.field if audit.field == 'id': model_name = cls.get_display_model_name(audit.model) if field.startswith('udf:'): field = field[4:] return format_string % { 'field': field, 'model': model_name, 'value': audit.current_display_value } def get_cleaned_data(self): # Grab each datatype and assign the sub-name to the # definition. These are used to clean the data cleaned_data = {} for subfield_name in self.data: sub_value = self.data.get(subfield_name, None) datatype = self.field_definition.datatype_by_field[subfield_name] try: sub_value = self.field_definition.clean_value( sub_value, datatype) except ValidationError: # If there was an error coming from the database # just continue with whatever the value was. pass cleaned_data[subfield_name] = sub_value cleaned_data['id'] = self.pk return cleaned_data def as_dict(self, *args, **kwargs): base_model_dict = super(UserDefinedCollectionValue, self).as_dict(*args, **kwargs) for field, value in self.data.iteritems(): base_model_dict['udf:' + field] = value return base_model_dict def apply_change(self, key, val): if key.startswith('udf:'): key = key[4:] self.data[key] = val else: try: super(UserDefinedCollectionValue, self)\ .apply_change(key, val) except ValueError: pass def save(self, *args, **kwargs): raise UserTrackingException( 'All changes to %s objects must be saved via "save_with_user"' % (self._model_name)) def save_with_user(self, user, *args, **kwargs): updated_fields = self._updated_fields() if self.pk is None: audit_type = Audit.Type.Insert else: audit_type = Audit.Type.Update field_perm = None model = self.field_definition.model_type field = 'udf:%s' % self.field_definition.name perms = permissions(user, self.field_definition.instance, model_name=model) for perm in perms: if perm.field_name == field and perm.allows_writes: field_perm = perm break if field_perm is None: raise AuthorizeException("Cannot save UDF field '%s.%s': " "No sufficient permission found." % (model, self.field_definition.name)) if field_perm.permission_level == FieldPermission.WRITE_WITH_AUDIT: model_id = _reserve_model_id(UserDefinedCollectionValue) pending = True for field, (oldval, _) in updated_fields.iteritems(): self.apply_change(field, oldval) else: pending = False super(UserDefinedCollectionValue, self).save_with_user(user, *args, **kwargs) model_id = self.pk if audit_type == Audit.Type.Insert: updated_fields['id'] = [None, model_id] for field, (old_val, new_val) in updated_fields.iteritems(): Audit.objects.create(current_value=new_val, previous_value=old_val, model='udf:%s' % self.field_definition.pk, model_id=model_id, field=field, instance=self.field_definition.instance, user=user, action=audit_type, requires_auth=pending)
class Link(BaseAccessLevel): """ Link Model Designed for both wireless and wired links """ type = models.SmallIntegerField(_('type'), null=True, blank=True, choices=choicify(LINK_TYPES), default=LINK_TYPES.get('radio')) # in most cases these two fields are mandatory, except for "planned" links interface_a = models.ForeignKey( Interface, verbose_name=_('from interface'), related_name='link_interface_from', blank=True, null=True, help_text= _('mandatory except for "planned" links (in planned links you might not have any device installed yet)' )) interface_b = models.ForeignKey( Interface, verbose_name=_('to interface'), related_name='link_interface_to', blank=True, null=True, help_text= _('mandatory except for "planned" links (in planned links you might not have any device installed yet)' )) topology = models.ForeignKey( Topology, blank=True, null=True, help_text=_('mandatory to draw the link dinamically')) # in "planned" links these two fields are necessary # while in all the other status they serve as a shortcut node_a = models.ForeignKey( Node, verbose_name=_('from node'), related_name='link_node_from', blank=True, null=True, help_text= _('leave blank (except for planned nodes) as it will be filled in automatically' )) node_b = models.ForeignKey( Node, verbose_name=_('to node'), related_name='link_node_to', blank=True, null=True, help_text= _('leave blank (except for planned nodes) as it will be filled in automatically' )) # shortcut layer = models.ForeignKey( Layer, verbose_name=_('layer'), blank=True, null=True, help_text=_('leave blank - it will be filled in automatically')) # geospatial info line = models.LineStringField( blank=True, null=True, help_text=_('leave blank and the line will be drawn automatically')) # monitoring info status = models.SmallIntegerField(_('status'), choices=choicify(LINK_STATUS), default=LINK_STATUS.get('planned')) first_seen = models.DateTimeField(_('first time seen on'), blank=True, null=True, default=None) last_seen = models.DateTimeField(_('last time seen on'), blank=True, null=True, default=None) # technical info metric_type = models.CharField(_('metric type'), max_length=6, choices=choicify(METRIC_TYPES), blank=True, null=True) metric_value = models.FloatField(_('metric value'), blank=True, null=True) max_rate = models.IntegerField(_('Maximum BPS'), null=True, default=None, blank=True) min_rate = models.IntegerField(_('Minimum BPS'), null=True, default=None, blank=True) # wireless specific info dbm = models.IntegerField(_('dBm average'), null=True, default=None, blank=True) noise = models.IntegerField(_('noise average'), null=True, default=None, blank=True) # additional data data = DictionaryField( _('extra data'), null=True, blank=True, help_text=_('store extra attributes in JSON string')) shortcuts = ReferencesField(null=True, blank=True) # django manager objects = LinkManager() class Meta: app_label = 'links' def __unicode__(self): return _(u'%s <> %s') % (self.node_a_name, self.node_b_name) def clean(self, *args, **kwargs): """ Custom validation 1. interface_a and interface_b mandatory except for planned links 2. planned links should have at least node_a and node_b filled in 3. dbm and noise fields can be filled only for radio links 4. interface_a and interface_b must differ 5. interface a and b type must match """ if self.status != LINK_STATUS.get('planned'): if self.interface_a is None or self.interface_b is None: raise ValidationError( _('fields "from interface" and "to interface" are mandatory in this case' )) if (self.interface_a_id == self.interface_b_id) or (self.interface_a == self.interface_b): msg = _( 'link cannot have same "from interface" and "to interface: %s"' ) % self.interface_a raise ValidationError(msg) if self.status == LINK_STATUS.get('planned') and ( self.node_a is None or self.node_b is None): raise ValidationError( _('fields "from node" and "to node" are mandatory for planned links' )) if self.type != LINK_TYPES.get('radio') and (self.dbm is not None or self.noise is not None): raise ValidationError( _('Only links of type "radio" can contain "dbm" and "noise" information' )) def save(self, *args, **kwargs): """ Custom save does the following: * determine link type if not specified * automatically fill 'node_a' and 'node_b' fields if necessary * draw line between two nodes * fill shortcut properties node_a_name and node_b_name """ if not self.type: if self.interface_a.type == INTERFACE_TYPES.get('wireless'): self.type = LINK_TYPES.get('radio') elif self.interface_a.type == INTERFACE_TYPES.get('ethernet'): self.type = LINK_TYPES.get('ethernet') else: self.type = LINK_TYPES.get('virtual') if self.interface_a_id: self.interface_a = Interface.objects.get(pk=self.interface_a_id) if self.interface_b_id: self.interface_b = Interface.objects.get(pk=self.interface_b_id) # fill in node_a and node_b if self.node_a is None and self.interface_a is not None: self.node_a = self.interface_a.node if self.node_b is None and self.interface_b is not None: self.node_b = self.interface_b.node # fill layer from node_a if self.layer is None: self.layer = self.node_a.layer # draw linestring if not self.line: self.line = LineString(self.node_a.point, self.node_b.point) # fill properties if self.data.get('node_a_name', None) is None: self.data['node_a_name'] = self.node_a.name self.data['node_b_name'] = self.node_b.name if self.data.get('node_a_slug', None) is None or self.data.get( 'node_b_slug', None) is None: self.data['node_a_slug'] = self.node_a.slug self.data['node_b_slug'] = self.node_b.slug if self.interface_a and self.data.get('interface_a_mac', None) is None: self.data['interface_a_mac'] = self.interface_a.mac if self.interface_b and self.data.get('interface_b_mac', None) is None: self.data['interface_b_mac'] = self.interface_b.mac if self.data.get('layer_slug') != self.layer.slug: self.data['layer_slug'] = self.layer.slug super(Link, self).save(*args, **kwargs) @classmethod def get_link(cls, source, target, topology=None): """ Find link between source and target, (or vice versa, order is irrelevant). :param source: ip or mac addresses :param target: ip or mac addresses :param topology: optional topology relation :returns: Link object :raises: LinkNotFound """ a = source b = target # ensure parameters are coherent if not (valid_ipv4(a) and valid_ipv4(b)) and not ( valid_ipv6(a) and valid_ipv6(b)) and not (valid_mac(a) and valid_mac(b)): raise ValueError('Expecting valid ipv4, ipv6 or mac address') # get interfaces a = cls._get_link_interface(a) b = cls._get_link_interface(b) # raise LinkDataNotFound if an interface is not found not_found = [] if a is None: not_found.append(source) if b is None: not_found.append(target) if not_found: msg = 'the following interfaces could not be found: {0}'.format( ', '.join(not_found)) raise LinkDataNotFound(msg) # find link with interfaces # inverse order is also ok q = (Q(interface_a=a, interface_b=b) | Q(interface_a=b, interface_b=a)) # add topology to lookup if topology: q = q & Q(topology=topology) link = Link.objects.filter(q).first() if link is None: raise LinkNotFound('Link matching query does not exist', interface_a=a, interface_b=b, topology=topology) return link @classmethod def _get_link_interface(self, string_id): if valid_ipv4(string_id) or valid_ipv6(string_id): try: return Ip.objects.get(address=string_id).interface except Ip.DoesNotExist as e: return None else: try: return Interface.objects.get(mac=string_id) except Interface.DoesNotExist as e: return None @classmethod def get_or_create(cls, source, target, cost, topology=None): """ Tries to find a link with get_link, creates a new link if link not found. """ try: return cls.get_link(source, target, topology) except LinkNotFound as e: pass # create link link = Link(interface_a=e.interface_a, interface_b=e.interface_b, status=LINK_STATUS['active'], metric_value=cost, topology=topology) link.full_clean() link.save() return link @property def node_a_name(self): return self.data.get('node_a_name', None) @property def node_b_name(self): return self.data.get('node_b_name', None) @property def node_a_slug(self): return self.data.get('node_a_slug', None) @property def node_b_slug(self): return self.data.get('node_b_slug', None) @property def interface_a_mac(self): return self.data.get('interface_a_mac', None) @property def interface_b_mac(self): return self.data.get('interface_b_mac', None) @property def layer_slug(self): return self.data.get('layer_slug', None) @property def quality(self): """ Quality is a number between 1 and 6 that rates the quality of the link. The way quality is calculated might be overridden by settings. 0 means unknown """ if self.metric_value is None: return 0 # PLACEHOLDER return 6 def ensure(self, status, cost): """ ensure link properties correspond to the specified ones perform save operation only if necessary """ changed = False status_id = LINK_STATUS[status] if self.status != status_id: self.status = status_id changed = True if self.metric_value != cost: self.metric_value = cost changed = True if changed: self.save()
class DataResource(Displayable): """Represents a file that has been uploaded to Geoanalytics for representation""" original_file = models.FileField(upload_to='geographica_resources', null=True, blank=True) resource_file = models.FileField(upload_to='geographica_resources', null=True, blank=True) resource_url = models.URLField(null=True, blank=True) metadata_url = models.URLField(null=True, blank=True) metadata_xml = models.TextField(null=True, blank=True) driver_config = DictionaryField(null=True, blank=True) metadata_properties = DictionaryField(null=True, blank=True) last_change = models.DateTimeField(null=True, blank=True, auto_now=True) last_refresh = models.DateTimeField( null=True, blank=True ) # updates happen only to geocms that were not uploaded by the user. next_refresh = models.DateTimeField( null=True, blank=True, db_index=True) # will be populated every time the update manager runs refresh_every = TimedeltaField(null=True, blank=True) md5sum = models.CharField(max_length=64, blank=True, null=True) # the unique md5 sum of the data bounding_box = models.PolygonField(null=True, srid=4326, blank=True) import_log = models.TextField(null=True, blank=True) associated_pages = models.ManyToManyField("pages.Page", blank=True, null=True, related_name='data_resources') driver = models.CharField( default='terrapyn.geocms.drivers.spatialite', max_length=255, null=False, blank=False, choices=getattr(settings, 'INSTALLED_DATARESOURCE_DRIVERS', ( ('terrapyn.geocms.drivers.spatialite', 'Spatialite (universal vector)'), ('terrapyn.geocms.drivers.shapefile', 'Shapefile'), ('terrapyn.geocms.drivers.geotiff', 'GeoTIFF'), ('terrapyn.geocms.drivers.postgis', 'PostGIS'), ('terrapyn.geocms.drivers.kmz', 'Google Earth KMZ'), ('terrapyn.geocms.drivers.ogr', 'OGR DataSource'), ))) big = models.BooleanField( default=False, help_text='Set this to be true if the dataset is more than 100MB' ) # causes certain drivers to optimize for datasets larger than memory def get_absolute_url(self): return reverse('resource-page', kwargs={'slug': self.slug}) def get_admin_url(self): return reverse("admin:geocms_dataresource_change", args=(self.id, )) @property def srs(self): if not self.metadata.native_srs: self.driver_instance.compute_spatial_metadata() srs = osr.SpatialReference() srs.ImportFromProj4(self.metadata.native_srs.encode('ascii')) return srs @property def driver_instance(self): if not hasattr(self, '_driver_instance'): self._driver_instance = get_driver(self.driver)(self) return self._driver_instance def __unicode__(self): return self.title class Meta: permissions = ( ('view_dataresource', "View data resource"), # to add beyond the default )