Example #1
0
class Link(BaseAccessLevel):
    """
    Link Model
    Intended for both wireless and wired 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'))
    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'))
    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 if necessary'))
    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 if necessary'))
    type = models.SmallIntegerField(_('type'), max_length=10, choices=choicify(LINK_TYPE), default=LINK_TYPE.get('radio'))
    status = models.SmallIntegerField(_('status'), choices=choicify(LINK_STATUS), default=LINK_STATUS.get('planned'))
    metric_type = models.CharField(_('metric type'), max_length=6, choices=choicify(METRIC_TYPES), blank=True)
    metric_value = models.FloatField(_('metric value'), blank=True, null=True)
    tx_rate = models.IntegerField(_('TX rate average'), null=True, default=None, blank=True)
    rx_rate = models.IntegerField(_('RX rate average'), null=True, default=None, blank=True)
    dbm = models.IntegerField(_('dBm average'), null=True, default=None, blank=True)
    noise = models.IntegerField(_('noise average'), null=True, default=None, blank=True)
    
    # manager
    objects = LinkManager()
    
    class Meta:
        permissions = (('can_view_links', 'Can view links'),)
    
    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
        """
        
        if self.status != LINK_STATUS.get('planned') and (self.interface_a == None or self.interface_b == 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 == None or self.node_b == None):
            raise ValidationError(_('fields "from node" and "to node" are mandatory for planned links'))
        
        if self.dbm != None or self.noise != None:
            raise ValidationError(_('Only links of type "radio" can contain "dbm" and "noise" information'))
    
    def save(self, *args, **kwargs):
        """ Automatically fill 'node_a' and 'node_b' fields if necessary """
        
        if self.interface_a != None:
            self.node_a = self.interface_a.device.node
        
        if self.interface_b != None:
            self.node_b = self.interface_b.device.node
        
        super(Link, self).save(*args, **kwargs)
Example #2
0
    ('babel', 'Babel'),
    ('802.11s', 'Open 802.11s'),
    ('bgp', 'BGP'),
    ('ospf', 'OSPF'),
    ('static', _('Static Routing')),
)

DEVICE_TYPES = {
    'radio device': 'radio',
    'server': 'server',
    'router': 'router',
    'switch managed': 'switch',
    'sensor': 'sensor',
    'other': 'other',
}
DEVICE_TYPES_CHOICES = choicify(DEVICE_TYPES)

DEVICE_STATUS = {
    'not_reachable': 0,  # device is not reachable
    'reachable': 1,  # device is reachable
    'unknown': 2,  # device has not been seen by the system yet
    'inactive': 3,  # manually deactivated by user or admin
}
DEVICE_STATUS_CHOICES = choicify(DEVICE_STATUS)

WIRELESS_MODE = (
    ('sta', _('station')),
    ('ap', _('access point')),
    ('adhoc', _('adhoc')),
    ('monitor', _('monitor')),
    ('mesh', _('mesh')),
Example #3
0
    'users': str(FILTER_CHOICES[2][0])
}

OUTWARD_STATUS_CHOICES = ((-1, _('error')), (0, _('draft')), (1,
                                                              _('scheduled')),
                          (2, _('sent')), (3, _('cancelled')))

# this is just for convenience and readability
OUTWARD_STATUS = {
    'error': OUTWARD_STATUS_CHOICES[0][0],
    'draft': OUTWARD_STATUS_CHOICES[1][0],
    'scheduled': OUTWARD_STATUS_CHOICES[2][0],
    'sent': OUTWARD_STATUS_CHOICES[3][0],
    'cancelled': OUTWARD_STATUS_CHOICES[4][0]
}

GROUPS = []
DEFAULT_GROUPS = ''
# convert strings to integers
for group in choicify(settings.NODESHOT['CHOICES']['ACCESS_LEVELS']):
    GROUPS += [(int(group[0]), group[1])]
    DEFAULT_GROUPS += '%s,' % group[0]
GROUPS += [(0, _('super users'))]
DEFAULT_GROUPS += '0'

INWARD_STATUS_CHOICES = (
    (-1, _('Error')),
    (0, _('Not sent yet')),
    (1, _('Sent')),
    (2, _('Cancelled')),
)
Example #4
0
}

OUTWARD_STATUS_CHOICES = ((-1, _('error')), (0, _('draft')), (1,
                                                              _('scheduled')),
                          (2, _('sent')), (3, _('cancelled')))

# this is just for convenience and readability
OUTWARD_STATUS = {
    'error': OUTWARD_STATUS_CHOICES[0][0],
    'draft': OUTWARD_STATUS_CHOICES[1][0],
    'scheduled': OUTWARD_STATUS_CHOICES[2][0],
    'sent': OUTWARD_STATUS_CHOICES[3][0],
    'cancelled': OUTWARD_STATUS_CHOICES[4][0]
}

GROUPS = []
DEFAULT_GROUPS = ''
# convert strings to integers
for group in choicify(ACCESS_LEVELS):
    GROUPS += [(int(group[0]), group[1])]
    DEFAULT_GROUPS += '%s,' % group[0]
GROUPS += [(0, _('super users'))]
DEFAULT_GROUPS += '0'

INWARD_STATUS_CHOICES = (
    (-1, _('Error')),
    (0, _('Not sent yet')),
    (1, _('Sent')),
    (2, _('Cancelled')),
)
    (0, _('draft')),
    (1, _('scheduled')),
    (2, _('sent')),
    (3, _('cancelled'))
)

# this is just for convenience and readability
OUTWARD_STATUS = {
    'error': OUTWARD_STATUS_CHOICES[0][0],
    'draft': OUTWARD_STATUS_CHOICES[1][0],
    'scheduled': OUTWARD_STATUS_CHOICES[2][0],
    'sent': OUTWARD_STATUS_CHOICES[3][0],
    'cancelled': OUTWARD_STATUS_CHOICES[4][0]
}

GROUPS = []
DEFAULT_GROUPS = ''
# convert strings to integers
for group in choicify(settings.NODESHOT['CHOICES']['ACCESS_LEVELS']):
    GROUPS += [(int(group[0]), group[1])]
    DEFAULT_GROUPS += '%s,' % group[0]
GROUPS += [(0, _('super users'))]
DEFAULT_GROUPS += '0'

INWARD_STATUS_CHOICES = (
    (-1, _('Error')),
    (0, _('Not sent yet')),
    (1, _('Sent')),
    (2, _('Cancelled')),
)
Example #6
0
from django.utils.translation import ugettext_lazy as _
from nodeshot.core.base.utils import choicify

POLARIZATIONS = {
    'horizonal': 1,
    'vertical': 2,
    'circular': 3,
    'linear': 4,
    'dual_linear': 5
}

POLARIZATION_CHOICES = choicify(POLARIZATIONS)
Example #7
0
from django.utils.translation import ugettext_lazy as _
from nodeshot.core.base.utils import choicify


SEX = {
    'male': 'M',
    'female': 'F'
}
SEX_CHOICES = choicify(SEX)
Example #8
0
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
Example #9
0
from nodeshot.core.base.utils import choicify


POLARIZATIONS = {
    'horizonal': 1,
    'vertical': 2,
    'circular': 3,
    'linear': 4,
    'dual_linear': 5
}

POLARIZATION_CHOICES = choicify(POLARIZATIONS)
Example #10
0
    'onu': 'onu',
    'other': 'other',
    'phone': 'phone',
    'ppanel': 'ppanel',
    'rack': 'rack',
    'radio device': 'radio',
    'router': 'router',
    'sensor': 'sensor',
    'server': 'server',
    'solar': 'solar',
    'splitter': 'splitter',
    'switch managed': 'switch',
    'torpedo': 'torpedo',
    'ups': 'ups',
}
DEVICE_TYPES_CHOICES = choicify(DEVICE_TYPES)

DEVICE_STATUS = {
    'not_reachable': 0,     # device is not reachable
    'reachable': 1,         # device is reachable
    'unknown': 2,           # device has not been seen by the system yet
    'inactive': 3,          # manually deactivated by user or admin
}
DEVICE_STATUS_CHOICES = choicify(DEVICE_STATUS)

WIRELESS_MODE = (
    ('sta', _('station')),
    ('ap', _('access point')),
    ('adhoc', _('adhoc')),
    ('monitor', _('monitor')),
    ('mesh', _('mesh')),
Example #11
0
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()
Example #12
0
class Node(BaseAccessLevel):
    """
    Nodes of a network, can be assigned to 'Layers' and should belong to 'Users'
    """
    name = models.CharField(_('name'), max_length=50, unique=True)
    slug = models.SlugField(max_length=50, db_index=True, unique=True)
    address = models.CharField(_('address'),
                               max_length=150,
                               blank=True,
                               null=True)
    status = models.SmallIntegerField(
        _('status'),
        max_length=3,
        choices=choicify(NODE_STATUS),
        default=NODE_STATUS.get(settings.NODESHOT['DEFAULTS']['NODE_STATUS'],
                                'potential'))
    is_published = models.BooleanField(
        default=settings.NODESHOT['DEFAULTS'].get('NODE_PUBLISHED', True))

    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')

    if 'nodeshot.interoperability' in settings.INSTALLED_APPS:
        # add reference to the external layer's ID
        external_id = models.PositiveIntegerField(blank=True, null=True)

    # nodes might be assigned to a foreign layer, therefore user can be left blank, requires custom validation
    user = models.ForeignKey(User, blank=True, null=True)

    # area if enabled
    if settings.NODESHOT['SETTINGS']['NODE_AREA']:
        area = models.PolygonField(blank=True, null=True)

    # positioning
    coords = models.PointField(_('coordinates'))
    elev = models.FloatField(_('elevation'), blank=True, null=True)

    description = models.TextField(_('description'),
                                   max_length=255,
                                   blank=True,
                                   null=True)
    notes = models.TextField(_('notes'), blank=True, null=True)

    # manager
    objects = GeoAccessLevelPublishedManager()

    # 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

    class Meta:
        db_table = 'nodes_node'
        app_label = 'nodes'
        permissions = (('can_view_nodes', 'Can view nodes'), )

        if 'nodeshot.interoperability' in settings.INSTALLED_APPS:
            # the combinations of layer_id and external_id must be unique
            unique_together = ('layer', 'external_id')

    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

    def clean(self, *args, **kwargs):
        #TODO: write test
        """
        Check  distance between nodes if feature is enabled.
        Check node is contained, in layer's area if defined
        """
        distance = settings.NODESHOT['DEFAULTS']['ZONE_MINIMUM_DISTANCE']
        coords = self.coords
        layer_area = self.layer.area
        near_nodes = Node.objects.filter(coords__distance_lte=(coords,
                                                               D(m=distance)))
        error_string_distance = "Distance between nodes cannot be less than %s meters" % (
            distance)
        error_string_not_contained_in_layer = "Node must be inside layer area"
        if len(near_nodes) > 0:
            raise ValidationError(error_string_distance)
        if layer_area is not None and not layer_area.contains(coords):
            raise ValidationError(error_string_not_contained_in_layer)

    def save(self, *args, **kwargs):
        super(Node, self).save(*args, **kwargs)
        # if status of a node changes
        if self.status != self._current_status:
            # send django signal
            node_status_changed.send(sender=self,
                                     old_status=self._current_status,
                                     new_status=self.status)
        # update __current_status
        self._current_status = self.status

    if 'grappelli' in settings.INSTALLED_APPS:

        @staticmethod
        def autocomplete_search_fields():
            return ('name__icontains', )
Example #13
0
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)

    # 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']['ZONE_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(
        User,
        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']['ZONE_MINIMUM_DISTANCE'],
        help_text=_(
            'minimum distance between nodes, 0 means feature disabled'))
    write_access_level = models.SmallIntegerField(
        _('write access level'),
        choices=choicify(ACCESS_LEVELS),
        default=ACCESS_LEVELS.get('public'),
        help_text=_('minimum access level to insert new nodes in this layer'))
    # 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')

    def clean(self, *args, **kwargs):
        """
        Custom validation
        """
        pass
Example #14
0
    (0, _('draft')),
    (1, _('scheduled')),
    (2, _('sent')),
    (3, _('cancelled'))
)

# this is just for convenience and readability
OUTWARD_STATUS = {
    'error': OUTWARD_STATUS_CHOICES[0][0],
    'draft': OUTWARD_STATUS_CHOICES[1][0],
    'scheduled': OUTWARD_STATUS_CHOICES[2][0],
    'sent': OUTWARD_STATUS_CHOICES[3][0],
    'cancelled': OUTWARD_STATUS_CHOICES[4][0]
}

GROUPS = []
DEFAULT_GROUPS = ''
# convert strings to integers
for group in choicify(ACCESS_LEVELS):
    GROUPS += [(int(group[0]), group[1])]
    DEFAULT_GROUPS += '%s,' % group[0]
GROUPS += [(0, _('super users'))]
DEFAULT_GROUPS += '0'

INWARD_STATUS_CHOICES = (
    (-1, _('Error')),
    (0, _('Not sent yet')),
    (1, _('Sent')),
    (2, _('Cancelled')),
)
Example #15
0
from django.utils.translation import ugettext_lazy as _
from nodeshot.core.base.utils import choicify

SEX = {'male': 'M', 'female': 'F'}
SEX_CHOICES = choicify(SEX)