Beispiel #1
0
class Order(AdminAbsoluteUrlMixin,
            models.Model,
            metaclass=TransitionWorkflowBase):
    status = TransitionField(
        default=OrderStatus.new.id,
        choices=OrderStatus(),
    )
    remarks = models.CharField(max_length=255, blank=True, default='')

    @classmethod
    @transition_action(return_attachment=True)
    def pack(cls, instances, **kwargs):
        requester = kwargs.get('requester')
        path = os.path.join(tempfile.gettempdir(), 'test.txt')
        with open(path, 'w') as f:
            f.write('test')
        return add_attachment_from_disk(instances, path, requester,
                                        'pack action')

    @classmethod
    @transition_action(
        return_attachment=True,
        verbose_name='Go to post office',
        run_after=['pack'],
    )
    def go_to_post_office(cls, instances, **kwargs):
        pass

    @classmethod
    @transition_action(
        return_attachment=False, )
    def generate_exception(cls, instances, request, **kwargs):
        raise Exception('exception')
Beispiel #2
0
class AccessCard(AdminAbsoluteUrlMixin,
                 TimeStampMixin,
                 Regionalizable,
                 models.Model,
                 metaclass=TransitionWorkflowBaseWithPermissions):
    visual_number = models.CharField(
        max_length=255,
        null=False,
        blank=False,
        unique=True,
        help_text=_('Number visible on the access card'))
    system_number = models.CharField(
        max_length=255,
        null=False,
        blank=False,
        unique=True,
        help_text=_('Internal number in the access system'))
    issue_date = models.DateField(null=True,
                                  blank=True,
                                  help_text=_('Date of issue to the User'))
    notes = models.TextField(null=True,
                             blank=True,
                             help_text=_('Optional notes'))
    user = models.ForeignKey(RalphUser,
                             null=True,
                             blank=True,
                             related_name='+',
                             help_text=_('User of the card'),
                             on_delete=models.SET_NULL)
    owner = models.ForeignKey(RalphUser,
                              null=True,
                              blank=True,
                              related_name='+',
                              help_text=('Owner of the card'),
                              on_delete=models.SET_NULL)
    status = TransitionField(choices=AccessCardStatus(),
                             default=AccessCardStatus.new.id,
                             null=False,
                             blank=False,
                             help_text=_('Access card status'))
    access_zones = TreeManyToManyField(AccessZone,
                                       blank=True,
                                       related_name='access_cards')

    def __str__(self):
        return _('Access Card: {}').format(self.visual_number)

    @classmethod
    def get_autocomplete_queryset(cls):
        return cls._default_manager.exclude(
            status=AccessCardStatus.liquidated.id)

    @classmethod
    @transition_action()
    def unassign_user(cls, instances, **kwargs):
        for instance in instances:
            kwargs['history_kwargs'][instance.pk]['affected_user'] = str(
                instance.user)
            instance.user = None

    @classmethod
    @transition_action(
        form_fields={
            'user': {
                'field': forms.CharField(label=_('User')),
                'autocomplete_field': 'user',
                'default_value': partial(autocomplete_user, field_name='user')
            }
        }, )
    def assign_user(cls, instances, **kwargs):
        user = get_user_model().objects.get(pk=int(kwargs['user']))
        for instance in instances:
            instance.user = user

    @classmethod
    @transition_action(
        form_fields={
            'owner': {
                'field': forms.CharField(label=_('Owner')),
                'autocomplete_field': 'owner',
                'default_value': partial(autocomplete_user, field_name='owner')
            }
        },
        help_text=_('assign owner'),
    )
    def assign_owner(cls, instances, **kwargs):
        owner = get_user_model().objects.get(pk=int(kwargs['owner']))
        for instance in instances:
            instance.owner = owner

    @classmethod
    @transition_action()
    def unassign_owner(cls, instances, **kwargs):
        for instance in instances:
            kwargs['history_kwargs'][instance.pk]['affected_owner'] = str(
                instance.owner)
            instance.owner = None

    @classmethod
    @transition_action()
    def clear_access_zones(cls, instances, requester, **kwargs):
        for instance in instances:
            instance.access_zones.clear()

    @classmethod
    @transition_action(
        form_fields={'notes': {
            'field': forms.CharField(label=_('notes')),
        }})
    def add_notes(cls, instances, **kwargs):
        for instance in instances:
            instance.notes = '{}\n{}'.format(instance.notes, kwargs['notes'])

    @classmethod
    @transition_action(run_after=['release_report'])
    def assign_requester_as_an_owner(cls, instances, requester, **kwargs):
        """Assign current user as an owner"""
        for instance in instances:
            instance.owner = requester
            instance.save()

    @classmethod
    @transition_action(
        form_fields={
            'accept': {
                'field':
                forms.BooleanField(
                    label=_('I have read and fully understand and '
                            'accept the agreement.'))
            },
        })
    def accept_asset_release_agreement(cls, instances, requester, **kwargs):
        pass

    @classmethod
    @transition_action(form_fields={
        'report_language': {
            'field':
            forms.ModelChoiceField(
                label=_('Release report language'),
                queryset=ReportLanguage.objects.all().order_by('-default'),
                empty_label=None),
            'exclude_from_history':
            True
        }
    },
                       return_attachment=True,
                       run_after=['assign_owner', 'assign_user'])
    def release_report(cls, instances, requester, transition_id, **kwargs):
        report_name = get_report_name_for_transition_id(transition_id)
        return generate_report(instances=instances,
                               name=report_name,
                               requester=requester,
                               language=kwargs['report_language'],
                               context=cls._get_report_context(instances))

    @classmethod
    @transition_action(run_after=['release_report'])
    def send_attachments_to_user(cls, requester, transition_id, **kwargs):
        context_func = get_hook('back_office.transition_action.email_context')
        send_transition_attachments_to_user(requester=requester,
                                            transition_id=transition_id,
                                            context_func=context_func,
                                            **kwargs)

    @classmethod
    def _get_report_context(cls, instances):
        context = [{
            'visual_number': obj.visual_number,
        } for obj in instances]
        return context
Beispiel #3
0
class AsyncOrder(AdminAbsoluteUrlMixin,
                 models.Model,
                 metaclass=TransitionWorkflowBase):
    status = TransitionField(
        default=OrderStatus.new.id,
        choices=OrderStatus(),
    )
    name = models.CharField(max_length=100)
    counter = models.PositiveSmallIntegerField(default=1)
    username = models.CharField(max_length=100, null=True, blank=True)
    foo = models.ForeignKey(Foo, null=True, blank=True)

    @classmethod
    @transition_action(
        verbose_name='Long running action',
        form_fields={'name': {
            'field': forms.CharField(label='name'),
        }},
        is_async=True,
        run_after=['freezing_action'])
    def long_running_action(cls, instances, **kwargs):
        for instance in instances:
            instance.counter += 1
            instance.name = kwargs['name']
            instance.save()

    @classmethod
    @transition_action(verbose_name='Another long running action',
                       form_fields={
                           'foo': {
                               'field': forms.CharField(label='Foo'),
                               'autocomplete_field': 'foo',
                           }
                       },
                       is_async=True,
                       run_after=['long_running_action'])
    def long_running_action_with_precondition(cls, instances, **kwargs):
        instance = instances[0]  # only one instance in asyc action
        instance.counter += 1
        instance.save()
        kwargs['shared_params'][instance.pk]['counter'] = instance.counter
        kwargs['history_kwargs'][
            instance.pk]['hist_counter'] = instance.counter
        if instance.counter < 5:
            raise RescheduleAsyncTransitionActionLater()
        instance.foo = kwargs['foo']
        instance.save()

    @classmethod
    @transition_action(verbose_name='Assign user',
                       run_after=['long_running_action'])
    def assing_user(cls, instances, requester, **kwargs):
        for instance in instances:
            instance.username = requester.username
            instance.save()

    @classmethod
    @transition_action(
        verbose_name='Failing action',
        is_async=True,
    )
    def failing_action(cls, instances, **kwargs):
        kwargs['shared_params'][instances[0].pk]['test'] = 'failing'
        raise ValueError()

    @classmethod
    @transition_action(
        verbose_name='Freezing action',
        is_async=True,
    )
    def freezing_action(cls, instances, **kwargs):
        kwargs['shared_params'][instances[0].pk]['test'] = 'freezing'
        raise FreezeAsyncTransition()
Beispiel #4
0
class BackOfficeAsset(Regionalizable, Asset):
    _allow_in_dashboard = True

    warehouse = models.ForeignKey(Warehouse, on_delete=models.PROTECT)
    owner = models.ForeignKey(
        settings.AUTH_USER_MODEL,
        null=True,
        blank=True,
        related_name='assets_as_owner',
    )
    user = models.ForeignKey(
        settings.AUTH_USER_MODEL,
        null=True,
        blank=True,
        related_name='assets_as_user',
    )
    location = models.CharField(max_length=128, null=True, blank=True)
    purchase_order = models.CharField(max_length=50, null=True, blank=True)
    loan_end_date = models.DateField(
        null=True,
        blank=True,
        default=None,
        verbose_name=_('Loan end date'),
    )
    status = TransitionField(
        default=BackOfficeAssetStatus.new.id,
        choices=BackOfficeAssetStatus(),
    )
    imei = NullableCharField(max_length=18,
                             null=True,
                             blank=True,
                             unique=True,
                             verbose_name=_('IMEI'))
    imei2 = NullableCharField(max_length=18,
                              null=True,
                              blank=True,
                              unique=True,
                              verbose_name=_('IMEI 2'))
    office_infrastructure = models.ForeignKey(OfficeInfrastructure,
                                              null=True,
                                              blank=True)

    class Meta:
        verbose_name = _('Back Office Asset')
        verbose_name_plural = _('Back Office Assets')

    @property
    def country_code(self):
        iso2 = Country.name_from_id(int(self.region.country)).upper()
        return iso2_to_iso3(iso2)

    def __str__(self):
        return '{} (BC: {} / SN: {})'.format(self.hostname or '-', self.barcode
                                             or '-', self.sn or '-')

    def __repr__(self):
        return '<BackOfficeAsset: {}>'.format(self.id)

    def validate_imei(self, imei):
        return IMEI_SINCE_2003.match(imei) or IMEI_UNTIL_2003.match(imei)

    def clean(self):
        super().clean()
        if self.imei and not self.validate_imei(self.imei):
            raise ValidationError({
                'imei': _('%(imei)s is not IMEI format') % {
                    'imei': self.imei
                }
            })
        if self.imei2 and not self.validate_imei(self.imei2):
            raise ValidationError({
                'imei2': _('%(imei)s is not IMEI format') % {
                    'imei': self.imei2
                }  # noqa
            })

    def is_liquidated(self, date=None):
        date = date or datetime.date.today()
        # check if asset has status 'liquidated' and if yes, check if it has
        # this status on given date
        if (self.status == BackOfficeAssetStatus.liquidated
                and self._liquidated_at(date)):
            return True
        return False

    def generate_hostname(self, commit=True, template_vars=None):
        def render_template(template):
            template = Template(template)
            context = Context(template_vars or {})
            return template.render(context)

        logger.warning(
            'Generating new hostname for {} using {} old hostname {}'.format(
                self, template_vars, self.hostname))
        prefix = render_template(ASSET_HOSTNAME_TEMPLATE.get('prefix', ''), )
        postfix = render_template(ASSET_HOSTNAME_TEMPLATE.get('postfix', ''), )
        counter_length = ASSET_HOSTNAME_TEMPLATE.get('counter_length', 5)
        last_hostname = AssetLastHostname.increment_hostname(prefix, postfix)
        self.hostname = last_hostname.formatted_hostname(fill=counter_length)
        if commit:
            self.save()

    # TODO: add message when hostname was changed
    def _try_assign_hostname(self, commit=False, country=None, force=False):
        if self.model.category and self.model.category.code:
            template_vars = {
                'code': self.model.category.code,
                'country_code': country or self.country_code,
            }
            if (force or not self.hostname
                    or self.country_code not in self.hostname):
                self.generate_hostname(commit, template_vars)

    @classmethod
    def get_autocomplete_queryset(cls):
        return cls._default_manager.exclude(
            status=BackOfficeAssetStatus.liquidated.id)

    @classmethod
    @transition_action(form_fields={
        'user': {
            'field': forms.CharField(label=_('User')),
            'autocomplete_field': 'user',
            'default_value': partial(autocomplete_user, field_name='user')
        }
    },
                       run_after=['unassign_user'])
    def assign_user(cls, instances, **kwargs):
        user = get_user_model().objects.get(pk=int(kwargs['user']))
        for instance in instances:
            instance.user = user

    @classmethod
    @transition_action(
        form_fields={
            'owner': {
                'field': forms.CharField(label=_('Owner')),
                'autocomplete_field': 'owner',
                'default_value': partial(autocomplete_user, field_name='owner')
            }
        },
        help_text=_(
            'During this transition owner will be assigned as well as new '
            'hostname might be generated for asset (only for particular model '
            'categories and only if owner\'s country has changed)'),
        run_after=['unassign_owner'])
    def assign_owner(cls, instances, **kwargs):
        owner = get_user_model().objects.get(pk=int(kwargs['owner']))
        for instance in instances:
            instance.owner = owner

    @classmethod
    @transition_action(form_fields={
        'licences': {
            'field':
            forms.ModelMultipleChoiceField(
                queryset=Licence.objects.all(),
                label=_('Licence'),
                required=False,
            ),
            'autocomplete_field':
            'licence',
            'autocomplete_model':
            'licences.BaseObjectLicence',
            'widget_options': {
                'multi': True
            },
        }
    },
                       run_after=['unassign_licences'])
    def assign_licence(cls, instances, **kwargs):
        for instance in instances:
            for obj in kwargs['licences']:
                BaseObjectLicence.objects.get_or_create(
                    base_object=instance,
                    licence_id=obj.id,
                )

    @classmethod
    @transition_action(run_after=['loan_report', 'return_report'])
    def unassign_owner(cls, instances, **kwargs):
        for instance in instances:
            kwargs['history_kwargs'][instance.pk]['affected_owner'] = str(
                instance.owner)
            instance.owner = None

    @classmethod
    @transition_action(run_after=['loan_report', 'return_report'])
    def unassign_user(cls, instances, **kwargs):
        for instance in instances:
            kwargs['history_kwargs'][instance.pk]['affected_user'] = str(
                instance.user)
            instance.user = None

    @classmethod
    @transition_action(
        form_fields={
            'loan_end_date': {
                'field':
                forms.DateField(
                    label=_('Loan end date'),
                    widget=forms.TextInput(attrs={'class': 'datepicker'}))
            }
        }, )
    def assign_loan_end_date(cls, instances, **kwargs):
        for instance in instances:
            instance.loan_end_date = kwargs['loan_end_date']

    @classmethod
    @transition_action()
    def unassign_loan_end_date(cls, instances, **kwargs):
        for instance in instances:
            instance.loan_end_date = None

    @classmethod
    @transition_action(
        form_fields={
            'warehouse': {
                'field': forms.CharField(label=_('Warehouse')),
                'autocomplete_field': 'warehouse'
            }
        })
    def assign_warehouse(cls, instances, **kwargs):
        warehouse = Warehouse.objects.get(pk=int(kwargs['warehouse']))
        for instance in instances:
            instance.warehouse = warehouse

    @classmethod
    @transition_action(
        form_fields={
            'office_infrastructure': {
                'field': forms.CharField(label=_('Office infrastructure')),
                'autocomplete_field': 'office_infrastructure'
            }
        }, )
    def assign_office_infrastructure(cls, instances, **kwargs):
        office_inf = OfficeInfrastructure.objects.get(
            pk=int(kwargs['office_infrastructure']))
        for instance in instances:
            instance.office_infrastructure = office_inf

    @classmethod
    @transition_action(form_fields={
        'remarks': {
            'field': forms.CharField(label=_('Remarks')),
        }
    })
    def add_remarks(cls, instances, **kwargs):
        for instance in instances:
            instance.remarks = '{}\n{}'.format(instance.remarks,
                                               kwargs['remarks'])

    @classmethod
    @transition_action(form_fields={
        'task_url': {
            'field': forms.URLField(label=_('task URL')),
        }
    })
    def assign_task_url(cls, instances, **kwargs):
        for instance in instances:
            instance.task_url = kwargs['task_url']

    @classmethod
    @transition_action()
    def unassign_licences(cls, instances, **kwargs):
        BaseObjectLicence.objects.filter(base_object__in=instances).delete()

    @classmethod
    @transition_action(
        form_fields={
            'country': {
                'field':
                forms.ChoiceField(
                    label=_('Country'),
                    choices=Country(),
                    **{
                        'initial':
                        Country.from_name(
                            settings.CHANGE_HOSTNAME_ACTION_DEFAULT_COUNTRY.
                            lower()  # noqa: E501
                        ).id
                    } if settings.CHANGE_HOSTNAME_ACTION_DEFAULT_COUNTRY else
                    {}),
            }
        }, )
    def change_hostname(cls, instances, **kwargs):
        country_id = kwargs['country']
        country_name = Country.name_from_id(int(country_id)).upper()
        iso3_country_name = iso2_to_iso3(country_name)
        for instance in instances:
            instance._try_assign_hostname(country=iso3_country_name,
                                          force=True)

    @classmethod
    @transition_action(
        form_fields={
            'user': {
                'field': forms.CharField(label=_('User')),
                'autocomplete_field': 'user',
            },
            'owner': {
                'field': forms.CharField(label=_('Owner')),
                'autocomplete_field': 'owner',
                'condition': lambda obj, actions: bool(obj.owner),
            }
        })
    def change_user_and_owner(cls, instances, **kwargs):
        UserModel = get_user_model()  # noqa
        user_id = kwargs.get('user', None)
        user = UserModel.objects.get(id=user_id)
        owner_id = kwargs.get('owner', None)
        for instance in instances:
            instance.user = user
            if not owner_id:
                instance.owner = user
            else:
                instance.owner = UserModel.objects.get(id=owner_id)
            instance.location = user.location

    @classmethod
    def _get_report_context(cls, instances):
        data_instances = [{
            'sn': obj.sn,
            'model': str(obj.model),
            'imei': obj.imei,
            'imei2': obj.imei2,
            'barcode': obj.barcode,
        } for obj in instances]
        return data_instances

    @classmethod
    @transition_action(precondition=_check_assets_owner)
    def must_be_owner_of_asset(cls, instances, **kwargs):
        """Only a precondition matters"""
        pass

    @classmethod
    @transition_action(precondition=_check_assets_user)
    def must_be_user_of_asset(cls, instances, **kwargs):
        """Only a precondition matters"""
        pass

    @classmethod
    @transition_action(
        form_fields={
            'accept': {
                'field':
                forms.BooleanField(
                    label=_('I have read and fully understand and '
                            'accept the agreement.'))
            },
        })
    def accept_asset_release_agreement(cls, instances, requester, **kwargs):
        pass

    @classmethod
    @transition_action(run_after=['release_report'])
    def assign_requester_as_an_owner(cls, instances, requester, **kwargs):
        """Assign current user as an owner"""
        for instance in instances:
            instance.owner = requester
            instance.save()

    @classmethod
    @transition_action(form_fields={
        'report_language': {
            'field':
            forms.ModelChoiceField(
                label=_('Release report language'),
                queryset=ReportLanguage.objects.all().order_by('-default'),
                empty_label=None),
            'exclude_from_history':
            True
        }
    },
                       return_attachment=True,
                       run_after=['assign_owner', 'assign_user'])
    def release_report(cls, instances, requester, transition_id, **kwargs):
        report_name = get_report_name_for_transition_id(transition_id)
        return generate_report(instances=instances,
                               name=report_name,
                               requester=requester,
                               language=kwargs['report_language'],
                               context=cls._get_report_context(instances))

    @classmethod
    @transition_action(
        run_after=['release_report', 'return_report', 'loan_report'])
    def send_attachments_to_user(cls, requester, transition_id, **kwargs):
        context_func = get_hook('back_office.transition_action.email_context')
        send_transition_attachments_to_user(requester=requester,
                                            transition_id=transition_id,
                                            context_func=context_func,
                                            **kwargs)

    @classmethod
    @transition_action(
        form_fields={
            'report_language': {
                'field':
                forms.ModelChoiceField(
                    label=_('Return report language'),
                    queryset=ReportLanguage.objects.all().order_by('-default'),
                    empty_label=None),
                'exclude_from_history':
                True
            },
        },
        return_attachment=True,
        precondition=_check_user_assigned,
    )
    def return_report(cls, instances, requester, **kwargs):
        return generate_report(instances=instances,
                               name='return',
                               requester=requester,
                               language=kwargs['report_language'],
                               context=cls._get_report_context(instances))

    @classmethod
    @transition_action(
        form_fields={
            'report_language': {
                'field':
                forms.ModelChoiceField(
                    label=_('Loan report language'),
                    queryset=ReportLanguage.objects.all().order_by('-default'),
                    empty_label=None),
                'exclude_from_history':
                True
            }
        },
        return_attachment=True,
        run_after=['assign_owner', 'assign_user', 'assign_loan_end_date'])
    def loan_report(cls, instances, requester, **kwargs):
        return generate_report(name='loan',
                               requester=requester,
                               instances=instances,
                               language=kwargs['report_language'],
                               context=cls._get_report_context(instances))

    @classmethod
    @transition_action(verbose_name=_('Convert to DataCenter Asset'),
                       disable_save_object=True,
                       only_one_action=True,
                       form_fields={
                           'rack': {
                               'field': forms.CharField(label=_('Rack')),
                               'autocomplete_field': 'rack',
                               'autocomplete_model':
                               'data_center.DataCenterAsset'
                           },
                           'position': {
                               'field':
                               forms.IntegerField(label=_('Position')),
                           },
                           'model': {
                               'field': forms.CharField(label=_('Model')),
                               'autocomplete_field': 'model',
                               'autocomplete_model':
                               'data_center.DataCenterAsset'
                           },
                           'service_env': {
                               'field':
                               forms.CharField(label=_('Service env')),
                               'autocomplete_field': 'service_env',
                               'autocomplete_model':
                               'data_center.DataCenterAsset'
                           }
                       })
    def convert_to_data_center_asset(cls, instances, **kwargs):
        from ralph.data_center.models.physical import DataCenterAsset, Rack  # noqa
        from ralph.back_office.helpers import bo_asset_to_dc_asset_status_converter  # noqa
        with transaction.atomic():
            for i, instance in enumerate(instances):
                data_center_asset = DataCenterAsset()
                data_center_asset.rack = Rack.objects.get(pk=kwargs['rack'])
                data_center_asset.position = kwargs['position']
                data_center_asset.service_env = ServiceEnvironment.objects.get(
                    pk=kwargs['service_env'])
                data_center_asset.model = AssetModel.objects.get(
                    pk=kwargs['model'])
                target_status = int(
                    Transition.objects.values_list('target', flat=True).get(
                        pk=kwargs['transition_id'])  # noqa
                )
                data_center_asset.status = bo_asset_to_dc_asset_status_converter(  # noqa
                    instance.status, target_status)
                move_parents_models(instance,
                                    data_center_asset,
                                    exclude_copy_fields=[
                                        'rack', 'model', 'service_env',
                                        'status'
                                    ])
                data_center_asset.save()
                # Save new asset to list, required to redirect url.
                # RunTransitionView.get_success_url()
                instances[i] = data_center_asset

    @classmethod
    @transition_action()
    def assign_hostname_if_empty_or_country_not_match(cls, instances,
                                                      **kwargs):
        for instance in instances:
            instance._try_assign_hostname(commit=False, force=False)
Beispiel #5
0
class VirtualServer(PreviousStateMixin, DNSaaSPublisherMixin,
                    AdminAbsoluteUrlMixin, NetworkableBaseObject, BaseObject):
    # parent field for VirtualServer is hypervisor!
    # TODO: limit parent to DataCenterAsset and CloudHost
    status = TransitionField(
        default=VirtualServerStatus.new.id,
        choices=VirtualServerStatus(),
    )
    type = models.ForeignKey(VirtualServerType, related_name='virtual_servers')
    hostname = NullableCharField(
        blank=True,
        default=None,
        max_length=255,
        null=True,
        verbose_name=_('hostname'),
        unique=True,
    )
    sn = NullableCharField(
        max_length=200,
        verbose_name=_('SN'),
        blank=True,
        default=None,
        null=True,
        unique=True,
    )
    # TODO: remove this field
    cluster = models.ForeignKey(Cluster, blank=True, null=True)

    previous_dc_host_update_fields = ['hostname']
    _allow_in_dashboard = True

    @cached_property
    def polymorphic_parent(self):
        return self.parent.last_descendant if self.parent_id else None

    def get_location(self):
        if (self.parent_id and self.parent.content_type_id
                == ContentType.objects.get_for_model(DataCenterAsset).id):
            parent = self.parent.asset.datacenterasset
            location = parent.get_location()
            if parent.hostname:
                location.append(parent.hostname)
        else:
            location = []
        return location

    @property
    def model(self):
        return self.type

    @cached_property
    def rack_id(self):
        return self.rack.id if self.rack else None

    @cached_property
    def rack(self):
        if self.parent_id:
            polymorphic_parent = self.polymorphic_parent.last_descendant
            if (isinstance(polymorphic_parent, (DataCenterAsset, CloudHost))):
                return polymorphic_parent.rack
        return None

    class Meta:
        verbose_name = _('Virtual server (VM)')
        verbose_name_plural = _('Virtual servers (VM)')

    def __str__(self):
        return 'VirtualServer: {} ({})'.format(self.hostname, self.sn)
Beispiel #6
0
class Cluster(
    PreviousStateMixin,
    DNSaaSPublisherMixin,
    AdminAbsoluteUrlMixin,
    WithManagementIPMixin,
    NetworkableBaseObject,
    BaseObject,
    models.Model
):
    name = models.CharField(_('name'), max_length=255, blank=True, null=True)
    hostname = NullableCharField(
        unique=True,
        null=True,
        blank=True,
        max_length=255,
        verbose_name=_('hostname')
    )
    type = models.ForeignKey(ClusterType)
    base_objects = models.ManyToManyField(
        BaseObject,
        verbose_name=_('Assigned base objects'),
        through='BaseObjectCluster',
        related_name='+',
    )
    status = TransitionField(
        default=ClusterStatus.in_use.id,
        choices=ClusterStatus(),
    )

    def __str__(self):
        return '{} ({})'.format(self.name or self.hostname, self.type)

    def get_location(self):
        return self.masters[0].get_location() if self.masters else []

    @property
    def model(self):
        return self.type

    @cached_property
    def masters(self):
        return self.get_masters(cast_base_object=True)

    def get_masters(self, cast_base_object=False):
        # prevents cyclic import
        from ralph.virtual.models import CloudHost, VirtualServer  # noqa

        result = []
        for obj in self.baseobjectcluster_set.all():
            if obj.is_master:
                bo = obj.base_object
                # fetch final object if it's base object
                if cast_base_object and not isinstance(
                    bo,
                    # list equal to BaseObjectCluster.base_object.limit_models
                    (Database, DataCenterAsset, CloudHost, VirtualServer)
                ):
                    bo = bo.last_descendant
                result.append(bo)
        return result

    @cached_property
    def rack_id(self):
        return self.rack.id if self.rack else None

    @cached_property
    def rack(self):
        for master in self.masters:
            if isinstance(master, DataCenterAsset) and master.rack_id:
                return master.rack
        return None

    def _validate_name_hostname(self):
        if not self.name and not self.hostname:
            error_message = [_('At least one of name or hostname is required')]
            raise ValidationError(
                {'name': error_message, 'hostname': error_message}
            )

    def clean(self):
        errors = {}
        for validator in [
            super().clean,
            self._validate_name_hostname,
        ]:
            try:
                validator()
            except ValidationError as e:
                e.update_error_dict(errors)
        if errors:
            raise ValidationError(errors)
Beispiel #7
0
class AccessCard(AdminAbsoluteUrlMixin,
                 TimeStampMixin,
                 Regionalizable,
                 models.Model,
                 metaclass=TransitionWorkflowBaseWithPermissions):
    visual_number = models.CharField(
        max_length=255,
        null=False,
        blank=False,
        unique=True,
        help_text=_('Number visible on the access card'))
    system_number = models.CharField(
        max_length=255,
        null=False,
        blank=False,
        unique=True,
        help_text=_('Internal number in the access system'))
    issue_date = models.DateField(null=True,
                                  blank=True,
                                  help_text=_('Date of issue to the User'))
    notes = models.TextField(null=True,
                             blank=True,
                             help_text=_('Optional notes'))
    user = models.ForeignKey(RalphUser,
                             null=True,
                             blank=True,
                             related_name='+',
                             help_text=_('User of the card'),
                             on_delete=models.SET_NULL)
    owner = models.ForeignKey(RalphUser,
                              null=True,
                              blank=True,
                              related_name='+',
                              help_text=('Owner of the card'),
                              on_delete=models.SET_NULL)
    status = TransitionField(choices=AccessCardStatus(),
                             default=AccessCardStatus.new.id,
                             null=False,
                             blank=False,
                             help_text=_('Access card status'))
    access_zones = TreeManyToManyField(AccessZone,
                                       blank=True,
                                       related_name='access_cards')

    def __str__(self):
        return _('Access Card: {}').format(self.visual_number)

    @classmethod
    def get_autocomplete_queryset(cls):
        return cls._default_manager.exclude(
            status=AccessCardStatus.liquidated.id)

    @classmethod
    @transition_action()
    def unassign_user(cls, instances, **kwargs):
        for instance in instances:
            kwargs['history_kwargs'][instance.pk]['affected_user'] = str(
                instance.user)
            instance.user = None

    @classmethod
    @transition_action(
        form_fields={
            'user': {
                'field': forms.CharField(label=_('User')),
                'autocomplete_field': 'user',
                'default_value': partial(autocomplete_user, field_name='user')
            }
        }, )
    def assign_user(cls, instances, **kwargs):
        user = get_user_model().objects.get(pk=int(kwargs['user']))
        for instance in instances:
            instance.user = user

    @classmethod
    @transition_action(
        form_fields={
            'owner': {
                'field': forms.CharField(label=_('Owner')),
                'autocomplete_field': 'owner',
                'default_value': partial(autocomplete_user, field_name='owner')
            }
        },
        help_text=_('assign owner'),
    )
    def assign_owner(cls, instances, **kwargs):
        owner = get_user_model().objects.get(pk=int(kwargs['owner']))
        for instance in instances:
            instance.owner = owner

    @classmethod
    @transition_action()
    def unassign_owner(cls, instances, **kwargs):
        for instance in instances:
            kwargs['history_kwargs'][instance.pk]['affected_owner'] = str(
                instance.owner)
            instance.owner = None
Beispiel #8
0
class SIMCard(AdminAbsoluteUrlMixin, TimeStampMixin, models.Model,
              metaclass=TransitionWorkflowBase):
    pin1 = models.CharField(
        max_length=8, null=True, blank=True,
        help_text=_('Required numeric characters only.'),
        validators=PIN_CODE_VALIDATORS
    )
    puk1 = models.CharField(
        max_length=16, help_text=_('Required numeric characters only.'),
        validators=PUK_CODE_VALIDATORS
    )
    pin2 = models.CharField(
        max_length=8, null=True, blank=True,
        help_text=_('Required numeric characters only.'),
        validators=PIN_CODE_VALIDATORS
    )
    puk2 = models.CharField(
        max_length=16, null=True, blank=True,
        help_text=_('Required numeric characters only.'),
        validators=PUK_CODE_VALIDATORS)
    carrier = models.ForeignKey(
        CellularCarrier, on_delete=models.PROTECT,
    )
    card_number = models.CharField(
        max_length=22, unique=True,
        validators=[
            MinLengthValidator(1),
            MaxLengthValidator(22),
            RegexValidator(
                regex='^\d+$',
                message=_('Required numeric characters only.'),
            )
        ]
    )
    phone_number = models.CharField(
        max_length=16, unique=True, help_text=_('ex. +2920181234'),
        validators=[
            MinLengthValidator(1),
            MaxLengthValidator(16),
            RegexValidator(
                regex='^\+\d+$',
                message='Phone number must have +2920181234 format.'
            )
        ]
    )
    warehouse = models.ForeignKey(Warehouse, on_delete=models.PROTECT)
    owner = models.ForeignKey(
        settings.AUTH_USER_MODEL, null=True, blank=True,
        on_delete=models.SET_NULL,
        related_name='owned_simcards',
    )
    user = models.ForeignKey(
        settings.AUTH_USER_MODEL, null=True, blank=True,
        on_delete=models.SET_NULL,
        related_name='used_simcards',
    )
    status = TransitionField(
        default=SIMCardStatus.new.id,
        choices=SIMCardStatus(),
    )
    remarks = models.TextField(blank=True)
    quarantine_until = models.DateField(
        null=True, blank=True,
        help_text=_('End of quarantine date.')
    )
    features = models.ManyToManyField(
        SIMCardFeatures,
        blank=True,
    )
    property_of = models.ForeignKey(
        AssetHolder,
        on_delete=models.PROTECT,
        null=True,
        blank=True,
    )

    def __str__(self):
        return _('SIM Card: {}').format(self.phone_number)

    @classmethod
    @transition_action(
        form_fields={
            'user': {
                'field': forms.CharField(label=_('User')),
                'autocomplete_field': 'user',
                'default_value': partial(autocomplete_user, field_name='user')
            }
        },
        run_after=['unassign_user']
    )
    def assign_user(cls, instances, **kwargs):
        user = get_user_model().objects.get(pk=int(kwargs['user']))
        for instance in instances:
            instance.user = user

    @classmethod
    @transition_action(
        form_fields={
            'accept': {
                'field': forms.BooleanField(
                    label=_(
                        'I have read and fully understand and '
                        'accept the agreement.'
                    )
                )
            },
        }
    )
    def accept_asset_release_agreement(cls, instances, requester, **kwargs):
        pass

    @classmethod
    @transition_action(
        form_fields={
            'report_language': {
                'field': forms.ModelChoiceField(
                    label=_('Release report language'),
                    queryset=ReportLanguage.objects.all().order_by('-default'),
                    empty_label=None
                ),
                'exclude_from_history': True
            }
        },
        return_attachment=True,
        run_after=['assign_owner', 'assign_user']
    )
    def release_report(cls, instances, requester, transition_id, **kwargs):
        report_name = get_report_name_for_transition_id(transition_id)
        return generate_report(
            instances=instances, name=report_name, requester=requester,
            language=kwargs['report_language'],
            context=cls._get_report_context(instances)
        )

    @classmethod
    def _get_report_context(cls, instances):
        context = [
            {
                'card_number': obj.card_number,
                'carrier': obj.carrier.name,
                'pin1': obj.pin1,
                'puk1': obj.puk1,
                'phone_number': obj.phone_number,
            }
            for obj in instances
        ]
        return context

    @classmethod
    @transition_action(
        run_after=['release_report']
    )
    def assign_requester_as_an_owner(cls, instances, requester, **kwargs):
        """Assign current user as an owner"""
        for instance in instances:
            instance.owner = requester
            instance.save()

    @classmethod
    @transition_action(
        form_fields={
            'owner': {
                'field': forms.CharField(label=_('Owner')),
                'autocomplete_field': 'owner',
                'default_value': partial(autocomplete_user, field_name='owner')
            }
        },
        help_text=_('assign owner'),
        run_after=['unassign_owner']
    )
    def assign_owner(cls, instances, **kwargs):
        owner = get_user_model().objects.get(pk=int(kwargs['owner']))
        for instance in instances:
            instance.owner = owner

    @classmethod
    @transition_action(run_after=['release_report'])
    def send_attachments_to_user(
        cls, requester, transition_id, **kwargs
    ):
        context_func = get_hook(
            'back_office.transition_action.email_context'
        )
        send_transition_attachments_to_user(
            requester=requester,
            transition_id=transition_id,
            context_func=context_func,
            **kwargs
        )

    @classmethod
    @transition_action(
        form_fields={
            'warehouse': {
                'field': forms.CharField(label=_('Warehouse')),
                'autocomplete_field': 'warehouse',
                'default_value': partial(
                    autocomplete_user,
                    field_name='warehouse'
                )
            }
        }
    )
    def assign_warehouse(cls, instances, **kwargs):
        warehouse = Warehouse.objects.get(pk=int(kwargs['warehouse']))
        for instance in instances:
            instance.warehouse = warehouse

    @classmethod
    @transition_action(
        run_after=['loan_report', 'return_report']
    )
    def unassign_owner(cls, instances, **kwargs):
        for instance in instances:
            kwargs['history_kwargs'][instance.pk][
                'affected_owner'
            ] = str(instance.owner)
            instance.owner = None

    @classmethod
    @transition_action(
        run_after=['loan_report', 'return_report']
    )
    def unassign_user(cls, instances, **kwargs):
        for instance in instances:
            kwargs['history_kwargs'][instance.pk][
                'affected_user'
            ] = str(instance.user)
            instance.user = None

    @classmethod
    @transition_action(
        form_fields={
            'task_url': {
                'field': forms.URLField(label=_('task URL')),
            }
        }
    )
    def assign_task_url(cls, instances, **kwargs):
        for instance in instances:
            instance.task_url = kwargs['task_url']
Beispiel #9
0
class SIMCard(AdminAbsoluteUrlMixin,
              TimeStampMixin,
              models.Model,
              metaclass=TransitionWorkflowBase):
    pin1 = models.CharField(max_length=8,
                            null=True,
                            blank=True,
                            help_text=_('Required numeric characters only.'),
                            validators=PIN_CODE_VALIDATORS)
    puk1 = models.CharField(max_length=16,
                            help_text=_('Required numeric characters only.'),
                            validators=PUK_CODE_VALIDATORS)
    pin2 = models.CharField(max_length=8,
                            null=True,
                            blank=True,
                            help_text=_('Required numeric characters only.'),
                            validators=PIN_CODE_VALIDATORS)
    puk2 = models.CharField(max_length=16,
                            null=True,
                            blank=True,
                            help_text=_('Required numeric characters only.'),
                            validators=PUK_CODE_VALIDATORS)
    carrier = models.ForeignKey(
        CellularCarrier,
        on_delete=models.PROTECT,
    )
    card_number = models.CharField(
        max_length=22,
        unique=True,
        validators=[
            MinLengthValidator(1),
            MaxLengthValidator(22),
            RegexValidator(
                regex='^\d+$',
                message=_('Required numeric characters only.'),
            )
        ])
    phone_number = models.CharField(
        max_length=16,
        unique=True,
        help_text=_('ex. +2920181234'),
        validators=[
            MinLengthValidator(1),
            MaxLengthValidator(16),
            RegexValidator(
                regex='^\+\d+$',
                message='Phone number must have +2920181234 format.')
        ])
    warehouse = models.ForeignKey(Warehouse, on_delete=models.PROTECT)
    owner = models.ForeignKey(
        settings.AUTH_USER_MODEL,
        null=True,
        blank=True,
        on_delete=models.SET_NULL,
        related_name='owned_simcards',
    )
    user = models.ForeignKey(
        settings.AUTH_USER_MODEL,
        null=True,
        blank=True,
        on_delete=models.SET_NULL,
        related_name='used_simcards',
    )
    status = TransitionField(
        default=SIMCardStatus.new.id,
        choices=SIMCardStatus(),
    )
    remarks = models.TextField(blank=True)
    quarantine_until = models.DateField(null=True,
                                        blank=True,
                                        help_text=_('End of quarantine date.'))
    features = models.ManyToManyField(
        SIMCardFeatures,
        blank=True,
    )

    def __str__(self):
        return _('SIM Card: {}').format(self.phone_number)

    @classmethod
    @transition_action(form_fields={
        'user': {
            'field': forms.CharField(label=_('User')),
            'autocomplete_field': 'user',
            'default_value': partial(autocomplete_user, field_name='user')
        }
    },
                       run_after=['unassign_user'])
    def assign_user(cls, instances, **kwargs):
        user = get_user_model().objects.get(pk=int(kwargs['user']))
        for instance in instances:
            instance.user = user

    @classmethod
    @transition_action(form_fields={
        'owner': {
            'field': forms.CharField(label=_('Owner')),
            'autocomplete_field': 'owner',
            'default_value': partial(autocomplete_user, field_name='owner')
        }
    },
                       help_text=_('text'),
                       run_after=['unassign_owner'])
    def assign_owner(cls, instances, **kwargs):
        owner = get_user_model().objects.get(pk=int(kwargs['owner']))
        for instance in instances:
            instance.owner = owner

    @classmethod
    @transition_action(
        form_fields={
            'warehouse': {
                'field': forms.CharField(label=_('Warehouse')),
                'autocomplete_field': 'warehouse',
                'default_value': partial(autocomplete_user,
                                         field_name='warehouse')
            }
        })
    def assign_warehouse(cls, instances, **kwargs):
        warehouse = Warehouse.objects.get(pk=int(kwargs['warehouse']))
        for instance in instances:
            instance.warehouse = warehouse

    @classmethod
    @transition_action(run_after=['loan_report', 'return_report'])
    def unassign_owner(cls, instances, **kwargs):
        for instance in instances:
            kwargs['history_kwargs'][instance.pk]['affected_owner'] = str(
                instance.owner)
            instance.owner = None

    @classmethod
    @transition_action(run_after=['loan_report', 'return_report'])
    def unassign_user(cls, instances, **kwargs):
        for instance in instances:
            kwargs['history_kwargs'][instance.pk]['affected_user'] = str(
                instance.user)
            instance.user = None

    @classmethod
    @transition_action(form_fields={
        'task_url': {
            'field': forms.URLField(label=_('task URL')),
        }
    })
    def assign_task_url(cls, instances, **kwargs):
        for instance in instances:
            instance.task_url = kwargs['task_url']
Beispiel #10
0
class Accessory(
    AdminAbsoluteUrlMixin,
    TimeStampMixin,
    Regionalizable,
    models.Model,
    metaclass=TransitionWorkflowBaseWithPermissions
):
    manufacturer = models.ForeignKey(
        Manufacturer, on_delete=models.PROTECT, blank=True, null=True
    )
    category = TreeForeignKey(
        Category, null=True, related_name='+'
    )
    accessory_name = models.CharField(
        max_length=255,
        null=False,
        blank=False,
        help_text=_('Accessory name')
    )
    product_number = models.CharField(
        max_length=255,
        null=False,
        blank=False,
        unique=True,
        help_text=_('Number of accessories')
    )
    user = models.ManyToManyField(
        settings.AUTH_USER_MODEL,
        through='AccessoryUser',
        related_name='+'
    )
    owner = models.ForeignKey(
        RalphUser,
        null=True,
        blank=True,
        related_name='+',
        help_text=_('Accessory owner'),
        on_delete=models.SET_NULL
    )
    status = TransitionField(
        choices=AccessoryStatus(),
        default=AccessoryStatus.new.id,
        null=False,
        blank=False,
        help_text=_('Accessory status')
    )
    number_bought = models.IntegerField(
        verbose_name=_('number of purchased items')
    )
    warehouse = models.ForeignKey(
        Warehouse,
        on_delete=models.PROTECT
    )
    objects = models.Manager()

    @classmethod
    @transition_action(
        form_fields={
            'restock': {
                'field': forms.IntegerField(label=_('restock'),)
            }
        },
    )
    def restock(cls, instances, **kwargs):
        restock = int(kwargs['restock'])
        for instance in instances:
            instance.number_bought += restock

    @classmethod
    @transition_action(
        form_fields={
            'accessory_send': {
                'field': forms.IntegerField(label=_('accessory_send'),)
            }
        },
    )
    def accessory_send(cls, instances, **kwargs):
        accessory_send = int(kwargs['accessory_send'])
        for instance in instances:
            instance.number_bought -= accessory_send

    @classmethod
    @transition_action(
        form_fields={
            'user': {
                'field': forms.CharField(label=_('User')),
                'autocomplete_field': 'user',
            },
            'quantity': {
                'field': forms.IntegerField(label=_('Quantity')),
            }
        },
    )
    def release_accessories(cls, instances, **kwargs):
        user = get_user_model().objects.get(pk=int(kwargs['user']))
        quantity = kwargs['quantity']
        accessory_user, created = AccessoryUser.objects.get_or_create(
            user=user,
            accessory=instances[0],
            defaults={'quantity': quantity}
        )
        if not created:
            accessory_user.quantity += quantity
            accessory_user.save()

    polymorphic_objects = PolymorphicQuerySet.as_manager()
    objects_used_free = AccessoryUsedFreeManager()
    objects_used_free_with_related = AccessoryUsedFreeRelatedObjectsManager()

    def __str__(self):
        return "{} x {} - ({})".format(
            self.number_bought,
            self.accessory_name,
            self.product_number,
        )

    @cached_property
    def autocomplete_str(self):
        return "{} ({} free) x {} - ({})".format(
            self.number_bought,
            self.free,
            self.accessory_name,
            self.product_number,
        )

    @cached_property
    def used(self):
        if not self.pk:
            return 0
        try:
            return (self.user_count or 0)
        except AttributeError:
            users_qs = self.user.through.objects.filter(accessory=self)

            def get_sum(qs):
                return qs.aggregate(sum=Sum('quantity'))['sum'] or 0
            return sum(map(get_sum, [users_qs]))
    used._permission_field = 'number_bought'

    @cached_property
    def free(self):
        if not self.pk:
            return 0
        return self.number_bought - self.used
    free._permission_field = 'number_bought'
Beispiel #11
0
class DataCenterAsset(
    PreviousStateMixin,
    DNSaaSPublisherMixin,
    WithManagementIPMixin,
    NetworkableBaseObject,
    AutocompleteTooltipMixin,
    Asset
):
    _allow_in_dashboard = True
    previous_dc_host_update_fields = ['hostname']

    rack = models.ForeignKey(
        Rack, null=True, blank=False, on_delete=models.PROTECT
    )
    status = TransitionField(
        default=DataCenterAssetStatus.new.id,
        choices=DataCenterAssetStatus(),
    )
    position = models.IntegerField(null=True, blank=True)
    orientation = models.PositiveIntegerField(
        choices=Orientation(),
        default=Orientation.front.id,
    )
    slot_no = models.CharField(
        blank=True,
        help_text=_('Fill it if asset is blade server'),
        max_length=3,
        null=True,
        validators=[
            RegexValidator(
                regex=VALID_SLOT_NUMBER_FORMAT,
                message=_(
                    "Slot number should be a number from range 1-16 with "
                    "an optional postfix 'A' or 'B' (e.g. '16A')"
                ),
                code='invalid_slot_no'
            )
        ],
        verbose_name=_('slot number'),
    )
    firmware_version = models.CharField(
        null=True,
        blank=True,
        max_length=256,
        verbose_name=_('firmware version'),
    )
    bios_version = models.CharField(
        null=True,
        blank=True,
        max_length=256,
        verbose_name=_('BIOS version'),
    )
    connections = models.ManyToManyField(
        'self',
        through='Connection',
        symmetrical=False,
    )
    source = models.PositiveIntegerField(
        blank=True,
        choices=AssetSource(),
        db_index=True,
        null=True,
        verbose_name=_("source"),
    )
    delivery_date = models.DateField(null=True, blank=True)
    production_year = models.PositiveSmallIntegerField(null=True, blank=True)
    production_use_date = models.DateField(null=True, blank=True)

    autocomplete_tooltip_fields = [
        'rack',
        'barcode',
        'sn',
    ]
    _summary_fields = [
        ('hostname', 'Hostname'),
        ('location', 'Location'),
        ('model__name', 'Model'),
    ]

    class Meta:
        verbose_name = _('data center asset')
        verbose_name_plural = _('data center assets')

    def __str__(self):
        return '{} (BC: {} / SN: {})'.format(
            self.hostname or '-', self.barcode or '-', self.sn or '-'
        )

    def __repr__(self):
        return '<DataCenterAsset: {}>'.format(self.id)

    def save(self, *args, **kwargs):
        super().save(*args, **kwargs)
        if self.pk:
            # When changing rack we search and save all descendants
            if self._previous_state['rack_id'] != self.rack_id:
                DataCenterAsset.objects.filter(
                    parent=self
                ).update(rack=self.rack)
            # When changing position if is blade,
            # we search and save all descendants
            if self._previous_state['position'] != self.position:
                DataCenterAsset.objects.filter(
                    parent=self
                ).update(position=self.position)

    def get_orientation_desc(self):
        return Orientation.name_from_id(self.orientation)

    def get_location(self):
        location = []
        if self.rack:
            location.extend([
                self.rack.server_room.data_center.name,
                self.rack.server_room.name,
                self.rack.name
            ])
        if self.position:
            location.append(str(self.position))
        if self.slot_no:
            location.append(str(self.slot_no))
        return location

    @property
    def is_blade(self):
        if self.model_id and self.model.has_parent:
            return True
        return False

    @property
    def cores_count(self):
        """Returns cores count assigned to device in Ralph"""
        asset_cores_count = self.model.cores_count if self.model else 0
        return asset_cores_count

    @cached_property
    def location(self):
        """
        Additional column 'location' display filter by:
        data center, server_room, rack, position (if is blade)
        """
        base_url = reverse('admin:data_center_datacenterasset_changelist')
        position = self.position
        if self.is_blade:
            position = generate_html_link(
                base_url,
                label=position,
                params={
                    'rack': self.rack_id,
                    'position__start': self.position,
                    'position__end': self.position
                },
            )

        result = [
            generate_html_link(
                base_url,
                label=self.rack.server_room.data_center.name,
                params={
                    'rack__server_room__data_center':
                        self.rack.server_room.data_center_id
                },
            ),
            generate_html_link(
                base_url,
                label=self.rack.server_room.name,
                params={'rack__server_room': self.rack.server_room_id},
            ),
            generate_html_link(
                base_url,
                label=self.rack.name,
                params={'rack': self.rack_id},
            )
        ] if self.rack and self.rack.server_room else []

        if self.position:
            result.append(str(position))
        if self.slot_no:
            result.append(str(self.slot_no))

        return '&nbsp;/&nbsp;'.join(result) if self.rack else '&mdash;'

    def _validate_orientation(self):
        """
        Validate if orientation is valid for given position.
        """
        if self.position is None:
            return
        if self.position == 0 and not Orientation.is_width(self.orientation):
            msg = 'Valid orientations for picked position are: {}'.format(
                ', '.join(
                    choice.desc for choice in Orientation.WIDTH.choices
                )
            )
            raise ValidationError({'orientation': [msg]})
        if self.position > 0 and not Orientation.is_depth(self.orientation):
            msg = 'Valid orientations for picked position are: {}'.format(
                ', '.join(
                    choice.desc for choice in Orientation.DEPTH.choices
                )
            )
            raise ValidationError({'orientation': [msg]})

    def _validate_position(self):
        """
        Validate if position not empty when rack requires it.
        """
        if (
            self.rack and
            self.position is None and
            self.rack.require_position
        ):
            msg = 'Position is required for this rack'
            raise ValidationError({'position': [msg]})

    def _validate_position_in_rack(self):
        """
        Validate if position is in rack height range.
        """
        if (
            self.rack and
            self.position is not None and
            self.position > self.rack.max_u_height
        ):
            msg = 'Position is higher than "max u height" = {}'.format(
                self.rack.max_u_height,
            )
            raise ValidationError({'position': [msg]})
        if self.position is not None and self.position < 0:
            msg = 'Position should be 0 or greater'
            raise ValidationError({'position': msg})

    def _validate_slot_no(self):
        if self.model_id:
            if self.model.has_parent and not self.slot_no:
                raise ValidationError({
                    'slot_no': 'Slot number is required when asset is blade'
                })
            if not self.model.has_parent and self.slot_no:
                raise ValidationError({
                    'slot_no': (
                        'Slot number cannot be filled when asset is not blade'
                    )
                })
            if self.parent:
                dc_asset_with_slot_no = DataCenterAsset.objects.filter(
                    parent=self.parent, slot_no=self.slot_no,
                    orientation=self.orientation,
                ).exclude(pk=self.pk).first()
                if dc_asset_with_slot_no:
                    message = mark_safe(
                        (
                            'Slot is already occupied by: '
                            '<a href="{}" target="_blank">{}</a>'
                        ).format(
                            reverse(
                                'admin:data_center_datacenterasset_change',
                                args=[dc_asset_with_slot_no.id]
                            ),
                            dc_asset_with_slot_no
                        )
                    )
                    raise ValidationError({
                        'slot_no': message
                    })

    def clean(self):
        # TODO: this should be default logic of clean method;
        # we could register somehow validators (or take each func with
        # _validate prefix) and call it here
        errors = {}
        for validator in [
            super().clean,
            self._validate_orientation,
            self._validate_position,
            self._validate_position_in_rack,
            self._validate_slot_no
        ]:
            try:
                validator()
            except ValidationError as e:
                e.update_error_dict(errors)
        if errors:
            raise ValidationError(errors)

    def get_related_assets(self):
        """Returns the children of a blade chassis"""
        orientations = [Orientation.front, Orientation.back]
        assets_by_orientation = []
        for orientation in orientations:
            assets_by_orientation.append(list(
                DataCenterAsset.objects.select_related('model').filter(
                    parent=self,
                    orientation=orientation,
                    model__has_parent=True,
                ).exclude(id=self.id)
            ))
        assets = [
            Gap.generate_gaps(assets) for assets in assets_by_orientation
        ]
        return chain(*assets)

    @classmethod
    def get_autocomplete_queryset(cls):
        return cls._default_manager.exclude(
            status=DataCenterAssetStatus.liquidated.id
        )

    @classmethod
    @transition_action(
        verbose_name=_('Change rack'),
        form_fields={
            'rack': {
                'field': forms.CharField(widget=AutocompleteWidget(
                    field=rack, admin_site=ralph_site
                )),
            }
        }
    )
    def change_rack(cls, instances, **kwargs):
        rack = Rack.objects.get(pk=kwargs['rack'])
        for instance in instances:
            instance.rack = rack

    @classmethod
    @transition_action(
        verbose_name=_('Convert to BackOffice Asset'),
        disable_save_object=True,
        only_one_action=True,
        form_fields={
            'warehouse': {
                'field': forms.CharField(label=_('Warehouse')),
                'autocomplete_field': 'warehouse',
                'autocomplete_model': 'back_office.BackOfficeAsset'
            },
            'region': {
                'field': forms.CharField(label=_('Region')),
                'autocomplete_field': 'region',
                'autocomplete_model': 'back_office.BackOfficeAsset'
            }
        }
    )
    def convert_to_backoffice_asset(cls, instances, **kwargs):
        with transaction.atomic():
            for i, instance in enumerate(instances):
                back_office_asset = BackOfficeAsset()

                back_office_asset.region = Region.objects.get(
                    pk=kwargs['region']
                )
                back_office_asset.warehouse = Warehouse.objects.get(
                    pk=kwargs['warehouse']
                )
                target_status = int(
                    Transition.objects.values_list('target', flat=True).get(pk=kwargs['transition_id'])  # noqa
                )
                back_office_asset.status = dc_asset_to_bo_asset_status_converter(  # noqa
                    instance.status, target_status
                )
                move_parents_models(
                    instance, back_office_asset, exclude_copy_fields=['status']
                )
                # Save new asset to list, required to redirect url.
                # RunTransitionView.get_success_url()
                instances[i] = back_office_asset

    @classmethod
    @transition_action(
        verbose_name=_('Cleanup scm status'),
    )
    def cleanup_scm_statuscheck(cls, instances, **kwargs):
        with transaction.atomic():
            for instance in instances:
                try:
                    instance.scmstatuscheck.delete()
                except DataCenterAsset.scmstatuscheck.\
                        RelatedObjectDoesNotExist:
                    pass

    @classmethod
    @transition_action(
        verbose_name=_('Assign additional IP and hostname pair'),
        form_fields={
            'network_pk': {
                'field': forms.ChoiceField(
                    label=_('Select network')
                ),
                'choices': assign_additional_hostname_choices,
                'exclude_from_history': True,
            },
        },
    )
    def assign_additional_hostname(cls, instances, network_pk, **kwargs):
        """
        Assign new hostname for instances based on selected network.
        """
        network = Network.objects.get(pk=network_pk)
        env = network.network_environment
        with transaction.atomic():
            for instance in instances:
                ethernet = Ethernet.objects.create(base_object=instance)
                ethernet.ipaddress = network.issue_next_free_ip()
                ethernet.ipaddress.hostname = env.issue_next_free_hostname()
                ethernet.ipaddress.save()
                ethernet.save()
Beispiel #12
0
class Accessory(
    AdminAbsoluteUrlMixin,
    TimeStampMixin,
    Regionalizable,
    models.Model,
    metaclass=TransitionWorkflowBaseWithPermissions
):
    manufacturer = models.CharField(
        max_length=255,
        null=False,
        blank=False,
        unique=False,
        help_text=_('Accessory manufacturer')
    )
    accessory_type = models.CharField(
        max_length=255,
        null=False,
        blank=False,
        unique=False,
        help_text=_('Accessory type')
    )
    accessory_name = models.CharField(
        max_length=255,
        null=False,
        blank=False,
        unique=True,
        help_text=_('Accessory name')
    )
    product_number = models.CharField(
        max_length=255,
        null=False,
        blank=False,
        unique=True,
        help_text=_('Number of accessories')
    )
    user = models.ManyToManyField(
        settings.AUTH_USER_MODEL,
        through='AccessoryUser',
        related_name='+'
    )
    owner = models.ForeignKey(
        RalphUser,
        null=True,
        blank=True,
        related_name='+',
        help_text=_('Accessory owner'),
        on_delete=models.SET_NULL
    )
    status = TransitionField(
        choices=AccessoryStatus(),
        default=AccessoryStatus.new.id,
        null=False,
        blank=False,
        help_text=_('Accessory status')
    )
    number_bought = models.IntegerField(
        verbose_name=_('number of purchased items')
    )
    warehouse = models.ForeignKey(
        Warehouse,
        on_delete=models.PROTECT
    )

    @classmethod
    @transition_action(
        form_fields={
            'restock': {
                'field': forms.IntegerField(label=_('restock'),)
            }
        },
    )
    def restock(cls, instances, **kwargs):
        restock = int(kwargs['restock'])
        for instance in instances:
            instance.number_bought += restock

    @classmethod
    @transition_action(
        form_fields={
            'user': {
                'field': forms.CharField(label=_('User')),
                'autocomplete_field': 'user',
            },
            'quantity': {
                'field': forms.IntegerField(label=_('Quantity')),
            }
        },
    )
    def release_accessories(cls, instances, **kwargs):
        user = get_user_model().objects.get(pk=int(kwargs['user']))
        AccessoryUser.objects.create(
            user=user, quantity=kwargs['quantity'],
            accessory_id=instances[0].id
        )