<commit_before># -*- coding: utf-8 -*-
#
# django-codenerix-pos
#
# Copyright 2017 Centrologic Computational Logistic Center S.L.
#
# Project URL : http://www.codenerix.com
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import json
import uuid
import hashlib
from channels import Channel

from django.db import models
from django.utils.encoding import smart_text
from django.utils.translation import ugettext_lazy as _
from django.db.models import Q

from jsonfield import JSONField

from codenerix_payments.models import PaymentRequest

from codenerix.models import CodenerixModel
from codenerix_extensions.corporate.models import CorporateImage
from codenerix_products.models import ProductFinal
from codenerix_invoicing.models import BillingSeries
from codenerix_extensions.lib.cryptography import AESCipher

from codenerix.models import GenInterface
from codenerix.models_people import GenRole
from codenerix_extensions.helpers import get_external_method
from codenerix_pos.settings import CDNX_POS_PERMISSIONS


"""
Plant
    Zones
    CorporateImage
    BillingSeries

Zones
    POS (pantalla fisica)
        Hardware Los que tengo (muchos)
        Hardware Los que puedo usar (muchos)
        token --- pos client (software instalado, minimo el id)
        Salable products (POSProduct)

    Slot (mesas)


Hardware (ticket, dni, caja, dispositivo de firma, dispositivo de consulta)
    nombre
    configuracion
    tipo (ticket, dni, caja, firma, consulta)
    token
"""

# Changing this KEYS will affect to any client beacuse it is used for communication as a standard
KIND_POSHARDWARE_TICKET = "TICKET"
KIND_POSHARDWARE_DNIE = "DNIE"
KIND_POSHARDWARE_CASH = "CASH"
KIND_POSHARDWARE_WEIGHT = "WEIGHT"
KIND_POSHARDWARE_SIGN = "SIGN"
KIND_POSHARDWARE_QUERY = "QUERY"

KIND_POSHARDWARE = (
    (KIND_POSHARDWARE_TICKET, _("Ticket printer")),
    (KIND_POSHARDWARE_DNIE, _("DNIe card reader")),
    (KIND_POSHARDWARE_CASH, _("Cash drawer")),
    (KIND_POSHARDWARE_WEIGHT, _("Weight")),
    (KIND_POSHARDWARE_SIGN, _("Signature pad")),
    (KIND_POSHARDWARE_QUERY, _("Query service (Ex: Barcode)")),  # Barcode reader, Point of Information for clients, etc...
)


class POSPlant(CodenerixModel):
    """
    Plant
    """
    corporate_image = models.ForeignKey(CorporateImage, related_name='posplants', verbose_name=_("Corporate image"), blank=False, null=False)
    billing_series = models.ForeignKey(BillingSeries, related_name='posplants', verbose_name='Billing series', blank=False, null=False)
    name = models.CharField(_("Name"), max_length=250, blank=False, null=False, unique=True)

    def __unicode__(self):
        return self.__str__()

    def __str__(self):
        return u"{}".format(smart_text(self.name))

    def __fields__(self, info):
        fields = []
        fields.append(('name', _("Name")))
        return fields


class POSZone(CodenerixModel):
    """
    Zone
    """
    plant = models.ForeignKey(POSPlant, related_name='zones', verbose_name=_("Plant"), blank=False, null=False)
    name = models.CharField(_("Name"), max_length=250, blank=False, null=False, unique=True)

    def __unicode__(self):
        return self.__str__()

    def __str__(self):
        return u"{}".format(smart_text(self.name))

    def __fields__(self, info):
        fields = []
        fields.append(('plant', _("Plant")))
        fields.append(('name', _("Name")))
        return fields


class POSHardware(CodenerixModel):
    """
    Hardware
    """
    pos = models.ForeignKey("POS", related_name='hardwares', verbose_name=_("Hardware"), blank=True, null=True)
    kind = models.CharField(_("Kind"), max_length=6, choices=KIND_POSHARDWARE, blank=False, null=False)
    name = models.CharField(_("Name"), max_length=250, blank=False, null=False, unique=True)
    enable = models.BooleanField(_('Enable'), default=True)
    uuid = models.UUIDField(default=uuid.uuid4, editable=False, unique=True)
    config = JSONField(_("config"), blank=True, null=True)
    value = JSONField(_("config"), blank=True, null=True)

    def __unicode__(self):
        return self.__str__()

    def __str__(self):
        return u"{}".format(smart_text(self.name))

    def __fields__(self, info):
        fields = []
        fields.append(('pos', _("POS")))
        fields.append(('get_kind_display', _("Kind")))
        fields.append(('name', _("Name")))
        fields.append(('enable', _("Enable")))
        fields.append(('uuid', _("UUID")))
        fields.append(('config', _("Config")))
        fields.append(('value', _("Value")))
        return fields

    def save(self, *args, **kwargs):
        if 'doreset' in kwargs:
            doreset = kwargs.pop('doreset')
        else:
            doreset = True

        result = super(POSHardware, self).save(*args, **kwargs)
        if doreset:
            self.pos.reset_client()
        return result

    def recv(self, msg):
        self.value = msg
        self.save(doreset=False)

    def send(self, msg=None, ref=None):
        '''
        Example of msg for each POSHARDWARE:
            TICKET: {'data': 'print this text, process thid dictionary or take my money'}
            CASH:   {'data': '...ANYTHING except None to open the Cash Drawer...' }
            DNIE:   {'data': '...ANYTHING except None to get again data from DNIe if connected...' }
            WEIGHT: {'data': '...ANYTHING except None to get the value of the last wegith' }
            SIGN:   --- NOT ALLOWED / NOT AVAILABLE ---
            QUERY:  --- NOT ALLOWED / NOT AVAILABLE ---
        '''
        if self.kind in ['TICKET', 'SIGN']:
            if msg is not None:
                data = msg
            else:
                raise IOError("Nothing to say to the remote endpoint???")
        elif self.kind in ['CASH', 'DNIE', 'WEIGHT']:
            data = 'DOIT'
        else:
            raise IOError("This Hardware can not send data anywhere")

        # Say it to send this message
        self.pos.send(data, ref, self.uuid)


class POS(CodenerixModel):
    '''
    Point of Service
    '''
    name = models.CharField(_("Name"), max_length=250, blank=False, null=False, unique=True)
    uuid = models.UUIDField(default=uuid.uuid4, editable=False, unique=True)
    key = models.CharField(_("Key"), max_length=32, blank=False, null=False, unique=True)
    zone = models.ForeignKey(POSZone, related_name='poss', verbose_name=_("Zone"))
    payments = models.ManyToManyField(PaymentRequest, related_name='poss', verbose_name=_("Payments"), blank=True, null=True)
    channel = models.CharField(_("Channel"), max_length=50, blank=True, null=True, unique=True, editable=False)
    # Hardware that can use
    hardware = models.ManyToManyField(POSHardware, related_name='poss', verbose_name=_("Hardware it can use"), blank=True, null=True)

    def __unicode__(self):
        return self.__str__()

    def __str__(self):
        return u"{}".format(smart_text(self.name))

    def __fields__(self, info):
        fields = []
        fields.append(('zone', _("Zone")))
        fields.append(('name', _("Name")))
        fields.append(('uuid', _("UUID")))
        fields.append(('key', _("Key")))
        fields.append(('channel', _("Channel")))
        fields.append(('hardware', _("Hardware")))
        return fields

    def have_cash_drawer(self):
        return self.hardwares.filter(kind=KIND_POSHARDWARE_CASH).exists()

    def save(self, *args, **kwargs):
        if 'doreset' in kwargs:
            doreset = kwargs.pop('doreset')
        else:
            doreset = True
        result = super(POS, self).save(*args, **kwargs)
        if doreset:
            self.reset_client()
        return result

    def reset_client(self):
        self.send({'action': 'reset'})

    def ping(self, uid=None):
        if uid is None:
            uidtxt = None
        else:
            uidtxt = uid.hex
        ref = hashlib.sha1(uuid.uuid4().hex.encode('utf-8')).hexdigest()
        self.send({'action': 'ping', 'ref': ref, 'uuid': uidtxt})
        return ref

    def send(self, data, ref=None, uid=None):

        if self.channel:
            if uid:
                # Message for some client
                message = {
                    'action': 'msg',
                    'message': {
                        'data': data,
                    },
                    'uuid': uid.hex,
                }
            else:
                # Message for the server
                message = data

            # Send message
            crypto = AESCipher()
            msg = json.dumps({'request': message, 'ref': ref})
            request = crypto.encrypt(msg, self.key).decode('utf-8')
            data = json.dumps({'message': request})
            Channel(self.channel).send({'text': data})
        else:
            raise IOError("No channel available for this POS")


class POSSlot(CodenerixModel):
    '''
    Slots for Point of Service
    '''
    zone = models.ForeignKey(POSZone, related_name='slots', verbose_name=_("Zone"))
    name = models.CharField(_("Name"), max_length=250, blank=False, null=False, unique=True)
    # orders = models.ManyToManyField(SalesOrder, related_name='slots', editable=False, verbose_name=_("Orders"))
    pos_x = models.IntegerField(_('Pos X'), null=True, blank=True, default=None, editable=False)
    pos_y = models.IntegerField(_('Pos Y'), null=True, blank=True, default=None, editable=False)

    def __unicode__(self):
        return self.__str__()

    def __str__(self):
        return u"{}".format(smart_text(self.name))

    def __fields__(self, info):
        fields = []
        fields.append(('zone', _("Zone")))
        fields.append(('name', _("Name")))
        # fields.append(('orders', _("Orders")))
        return fields


class POSProduct(CodenerixModel):
    """
    Salable products in the POS
    """
    pos = models.ForeignKey(POS, related_name='posproducts', verbose_name=_("POS"))
    product = models.ForeignKey(ProductFinal, related_name='posproducts', verbose_name=_("Product"))
    enable = models.BooleanField(_('Enable'), default=True)

    class Meta(CodenerixModel.Meta):
        unique_together = (("pos", "product"))

    def __unicode__(self):
        return self.__str__()

    def __str__(self):
        return u"{} {}".format(smart_text(self.pos), smart_text(self.product))

    def __fields__(self, info):
        fields = []
        fields.append(('pos', _("POS")))
        fields.append(('product', _("Product")))
        fields.append(('enable', _("Enable")))
        return fields


class POSLog(CodenerixModel):
    """
    LOG for POS
    """
    pos = models.ForeignKey(POS, related_name='logs', verbose_name=_("POS"), editable=False, null=True)
    poshw = models.ForeignKey(POSHardware, related_name='logs', verbose_name=_("POS"), editable=False, null=True)
    uuid = models.UUIDField(default=uuid.uuid4, editable=False)
    log = JSONField(_("LOG"), blank=True, null=True)

    def __unicode__(self):
        return self.__str__()

    def __str__(self):
        return self.uuid.hex

    def __fields__(self, info):
        fields = []
        fields.append(('created', _("Created")))
        fields.append(('uuid', _("UUID")))
        fields.append(('pos', _("POS")))
        fields.append(('poshw', _("POSHardware")))
        fields.append(('log', _("Log")))
        return fields

    def __searchF__(self, info):

        # Build both lists
        poss = [(pos.pk, pos.name) for pos in POS.objects.all()]
        poshws = [(poshw.pk, poshw.name) for poshw in POSHardware.objects.all()]

        tf = {}
        tf['uuid'] = (_('UUID'), lambda x: Q(uuid__icontains=x), 'input')
        tf['pos'] = (_('POS'), lambda x: Q(pos__pk=x), poss)
        tf['poshw'] = (_('Hardware'), lambda x: Q(poshw__pk=x), poshws)
        return tf


# ############################
class ABSTRACT_GenPOSOperator(models.Model):  # META: Abstract class

    class Meta(object):
        abstract = True


class POSOperator(GenRole, CodenerixModel):
    class CodenerixMeta:
        abstract = ABSTRACT_GenPOSOperator
        rol_groups = {
            'POSOperator': CDNX_POS_PERMISSIONS['operator'],
        }
        rol_permissions = [
        ]

        force_methods = {
            'foreignkey_posoperator': ('CDNX_get_fk_info_posoperator', _('---')),
        }

    pos = models.ForeignKey(POS, related_name='pos_operators', verbose_name=_('POS'))
    enable = models.BooleanField(_("Enable"), blank=False, null=False, default=True)

    @staticmethod
    def foreignkey_external():
        return get_external_method(POSOperator, POSOperator.CodenerixMeta.force_methods['foreignkey_posoperator'][0])

    def __unicode__(self):
        return u"{}".format(smart_text(self.pk))

    def __str__(self):
        return self.__unicode__()

    def __fields__(self, info):
        fields = []
        fields.append(('pos', _("POS")))
        fields.append(('enable', _("Enable")))
        fields = get_external_method(POSOperator, '__fields_posoperator__', info, fields)
        return fields


# operators
class GenPOSOperator(GenInterface, ABSTRACT_GenPOSOperator):  # META: Abstract class
    pos_operator = models.OneToOneField(POSOperator, related_name='external', verbose_name=_("POS Operator"), null=True, on_delete=models.SET_NULL, blank=True)

    class Meta(GenInterface.Meta, ABSTRACT_GenPOSOperator.Meta):
        abstract = True

    @classmethod
    def permissions(cls):
        group = 'POSOperator'
        perms = []
        print(cls.posoperator.field.related_model)

        return None

        # print({group: {'gperm': None, 'dperm': perms, 'model': None},})
<commit_msg>Communication for POSHardware avaiable for all devices!<commit_after># -*- coding: utf-8 -*-
#
# django-codenerix-pos
#
# Copyright 2017 Centrologic Computational Logistic Center S.L.
#
# Project URL : http://www.codenerix.com
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import json
import uuid
import hashlib
from channels import Channel

from django.db import models
from django.utils.encoding import smart_text
from django.utils.translation import ugettext_lazy as _
from django.db.models import Q

from jsonfield import JSONField

from codenerix_payments.models import PaymentRequest

from codenerix.models import CodenerixModel
from codenerix_extensions.corporate.models import CorporateImage
from codenerix_products.models import ProductFinal
from codenerix_invoicing.models import BillingSeries
from codenerix_extensions.lib.cryptography import AESCipher

from codenerix.models import GenInterface
from codenerix.models_people import GenRole
from codenerix_extensions.helpers import get_external_method
from codenerix_pos.settings import CDNX_POS_PERMISSIONS


"""
Plant
    Zones
    CorporateImage
    BillingSeries

Zones
    POS (pantalla fisica)
        Hardware Los que tengo (muchos)
        Hardware Los que puedo usar (muchos)
        token --- pos client (software instalado, minimo el id)
        Salable products (POSProduct)

    Slot (mesas)


Hardware (ticket, dni, caja, dispositivo de firma, dispositivo de consulta)
    nombre
    configuracion
    tipo (ticket, dni, caja, firma, consulta)
    token
"""

# Changing this KEYS will affect to any client beacuse it is used for communication as a standard
KIND_POSHARDWARE_TICKET = "TICKET"
KIND_POSHARDWARE_DNIE = "DNIE"
KIND_POSHARDWARE_CASH = "CASH"
KIND_POSHARDWARE_WEIGHT = "WEIGHT"
KIND_POSHARDWARE_SIGN = "SIGN"
KIND_POSHARDWARE_QUERY = "QUERY"

KIND_POSHARDWARE = (
    (KIND_POSHARDWARE_TICKET, _("Ticket printer")),
    (KIND_POSHARDWARE_DNIE, _("DNIe card reader")),
    (KIND_POSHARDWARE_CASH, _("Cash drawer")),
    (KIND_POSHARDWARE_WEIGHT, _("Weight")),
    (KIND_POSHARDWARE_SIGN, _("Signature pad")),
    (KIND_POSHARDWARE_QUERY, _("Query service (Ex: Barcode)")),  # Barcode reader, Point of Information for clients, etc...
)


class POSPlant(CodenerixModel):
    """
    Plant
    """
    corporate_image = models.ForeignKey(CorporateImage, related_name='posplants', verbose_name=_("Corporate image"), blank=False, null=False)
    billing_series = models.ForeignKey(BillingSeries, related_name='posplants', verbose_name='Billing series', blank=False, null=False)
    name = models.CharField(_("Name"), max_length=250, blank=False, null=False, unique=True)

    def __unicode__(self):
        return self.__str__()

    def __str__(self):
        return u"{}".format(smart_text(self.name))

    def __fields__(self, info):
        fields = []
        fields.append(('name', _("Name")))
        return fields


class POSZone(CodenerixModel):
    """
    Zone
    """
    plant = models.ForeignKey(POSPlant, related_name='zones', verbose_name=_("Plant"), blank=False, null=False)
    name = models.CharField(_("Name"), max_length=250, blank=False, null=False, unique=True)

    def __unicode__(self):
        return self.__str__()

    def __str__(self):
        return u"{}".format(smart_text(self.name))

    def __fields__(self, info):
        fields = []
        fields.append(('plant', _("Plant")))
        fields.append(('name', _("Name")))
        return fields


class POSHardware(CodenerixModel):
    """
    Hardware
    """
    pos = models.ForeignKey("POS", related_name='hardwares', verbose_name=_("Hardware"), blank=True, null=True)
    kind = models.CharField(_("Kind"), max_length=6, choices=KIND_POSHARDWARE, blank=False, null=False)
    name = models.CharField(_("Name"), max_length=250, blank=False, null=False, unique=True)
    enable = models.BooleanField(_('Enable'), default=True)
    uuid = models.UUIDField(default=uuid.uuid4, editable=False, unique=True)
    config = JSONField(_("config"), blank=True, null=True)
    value = JSONField(_("config"), blank=True, null=True)

    def __unicode__(self):
        return self.__str__()

    def __str__(self):
        return u"{}".format(smart_text(self.name))

    def __fields__(self, info):
        fields = []
        fields.append(('pos', _("POS")))
        fields.append(('get_kind_display', _("Kind")))
        fields.append(('name', _("Name")))
        fields.append(('enable', _("Enable")))
        fields.append(('uuid', _("UUID")))
        fields.append(('config', _("Config")))
        fields.append(('value', _("Value")))
        return fields

    def save(self, *args, **kwargs):
        if 'doreset' in kwargs:
            doreset = kwargs.pop('doreset')
        else:
            doreset = True

        result = super(POSHardware, self).save(*args, **kwargs)
        if doreset:
            self.pos.reset_client()
        return result

    def recv(self, msg):
        self.value = msg
        self.save(doreset=False)

    def send(self, msg=None, ref=None):
        '''
        Example of msg for each POSHARDWARE:
            TICKET: {'data': 'print this text, process thid dictionary or take my money'}
            CASH:   {'data': '...ANYTHING except None to open the Cash Drawer...' }
            DNIE:   {'data': '...ANYTHING except None to get again data from DNIe if connected...' }
            WEIGHT: {'data': '...ANYTHING except None to get the value of the last wegith' }
            OTHERS: {'data': '...ANYTHING you need to communicate to the device'}
        '''
        if self.kind in ['CASH', 'DNIE', 'WEIGHT']:
            data = 'DOIT'
        else:
            if msg is not None:
                data = msg
            else:
                raise IOError("Nothing to say to the remote endpoint???")

        # Say it to send this message
        self.pos.send(data, ref, self.uuid)


class POS(CodenerixModel):
    '''
    Point of Service
    '''
    name = models.CharField(_("Name"), max_length=250, blank=False, null=False, unique=True)
    uuid = models.UUIDField(default=uuid.uuid4, editable=False, unique=True)
    key = models.CharField(_("Key"), max_length=32, blank=False, null=False, unique=True)
    zone = models.ForeignKey(POSZone, related_name='poss', verbose_name=_("Zone"))
    payments = models.ManyToManyField(PaymentRequest, related_name='poss', verbose_name=_("Payments"), blank=True, null=True)
    channel = models.CharField(_("Channel"), max_length=50, blank=True, null=True, unique=True, editable=False)
    # Hardware that can use
    hardware = models.ManyToManyField(POSHardware, related_name='poss', verbose_name=_("Hardware it can use"), blank=True, null=True)

    def __unicode__(self):
        return self.__str__()

    def __str__(self):
        return u"{}".format(smart_text(self.name))

    def __fields__(self, info):
        fields = []
        fields.append(('zone', _("Zone")))
        fields.append(('name', _("Name")))
        fields.append(('uuid', _("UUID")))
        fields.append(('key', _("Key")))
        fields.append(('channel', _("Channel")))
        fields.append(('hardware', _("Hardware")))
        return fields

    def have_cash_drawer(self):
        return self.hardwares.filter(kind=KIND_POSHARDWARE_CASH).exists()

    def save(self, *args, **kwargs):
        if 'doreset' in kwargs:
            doreset = kwargs.pop('doreset')
        else:
            doreset = True
        result = super(POS, self).save(*args, **kwargs)
        if doreset:
            self.reset_client()
        return result

    def reset_client(self):
        self.send({'action': 'reset'})

    def ping(self, uid=None):
        if uid is None:
            uidtxt = None
        else:
            uidtxt = uid.hex
        ref = hashlib.sha1(uuid.uuid4().hex.encode('utf-8')).hexdigest()
        self.send({'action': 'ping', 'ref': ref, 'uuid': uidtxt})
        return ref

    def send(self, data, ref=None, uid=None):

        if self.channel:
            if uid:
                # Message for some client
                message = {
                    'action': 'msg',
                    'message': {
                        'data': data,
                    },
                    'uuid': uid.hex,
                }
            else:
                # Message for the server
                message = data

            # Send message
            crypto = AESCipher()
            msg = json.dumps({'request': message, 'ref': ref})
            request = crypto.encrypt(msg, self.key).decode('utf-8')
            data = json.dumps({'message': request})
            Channel(self.channel).send({'text': data})
        else:
            raise IOError("No channel available for this POS")


class POSSlot(CodenerixModel):
    '''
    Slots for Point of Service
    '''
    zone = models.ForeignKey(POSZone, related_name='slots', verbose_name=_("Zone"))
    name = models.CharField(_("Name"), max_length=250, blank=False, null=False, unique=True)
    # orders = models.ManyToManyField(SalesOrder, related_name='slots', editable=False, verbose_name=_("Orders"))
    pos_x = models.IntegerField(_('Pos X'), null=True, blank=True, default=None, editable=False)
    pos_y = models.IntegerField(_('Pos Y'), null=True, blank=True, default=None, editable=False)

    def __unicode__(self):
        return self.__str__()

    def __str__(self):
        return u"{}".format(smart_text(self.name))

    def __fields__(self, info):
        fields = []
        fields.append(('zone', _("Zone")))
        fields.append(('name', _("Name")))
        # fields.append(('orders', _("Orders")))
        return fields


class POSProduct(CodenerixModel):
    """
    Salable products in the POS
    """
    pos = models.ForeignKey(POS, related_name='posproducts', verbose_name=_("POS"))
    product = models.ForeignKey(ProductFinal, related_name='posproducts', verbose_name=_("Product"))
    enable = models.BooleanField(_('Enable'), default=True)

    class Meta(CodenerixModel.Meta):
        unique_together = (("pos", "product"))

    def __unicode__(self):
        return self.__str__()

    def __str__(self):
        return u"{} {}".format(smart_text(self.pos), smart_text(self.product))

    def __fields__(self, info):
        fields = []
        fields.append(('pos', _("POS")))
        fields.append(('product', _("Product")))
        fields.append(('enable', _("Enable")))
        return fields


class POSLog(CodenerixModel):
    """
    LOG for POS
    """
    pos = models.ForeignKey(POS, related_name='logs', verbose_name=_("POS"), editable=False, null=True)
    poshw = models.ForeignKey(POSHardware, related_name='logs', verbose_name=_("POS"), editable=False, null=True)
    uuid = models.UUIDField(default=uuid.uuid4, editable=False)
    log = JSONField(_("LOG"), blank=True, null=True)

    def __unicode__(self):
        return self.__str__()

    def __str__(self):
        return self.uuid.hex

    def __fields__(self, info):
        fields = []
        fields.append(('created', _("Created")))
        fields.append(('uuid', _("UUID")))
        fields.append(('pos', _("POS")))
        fields.append(('poshw', _("POSHardware")))
        fields.append(('log', _("Log")))
        return fields

    def __searchF__(self, info):

        # Build both lists
        poss = [(pos.pk, pos.name) for pos in POS.objects.all()]
        poshws = [(poshw.pk, poshw.name) for poshw in POSHardware.objects.all()]

        tf = {}
        tf['uuid'] = (_('UUID'), lambda x: Q(uuid__icontains=x), 'input')
        tf['pos'] = (_('POS'), lambda x: Q(pos__pk=x), poss)
        tf['poshw'] = (_('Hardware'), lambda x: Q(poshw__pk=x), poshws)
        return tf


# ############################
class ABSTRACT_GenPOSOperator(models.Model):  # META: Abstract class

    class Meta(object):
        abstract = True


class POSOperator(GenRole, CodenerixModel):
    class CodenerixMeta:
        abstract = ABSTRACT_GenPOSOperator
        rol_groups = {
            'POSOperator': CDNX_POS_PERMISSIONS['operator'],
        }
        rol_permissions = [
        ]

        force_methods = {
            'foreignkey_posoperator': ('CDNX_get_fk_info_posoperator', _('---')),
        }

    pos = models.ForeignKey(POS, related_name='pos_operators', verbose_name=_('POS'))
    enable = models.BooleanField(_("Enable"), blank=False, null=False, default=True)

    @staticmethod
    def foreignkey_external():
        return get_external_method(POSOperator, POSOperator.CodenerixMeta.force_methods['foreignkey_posoperator'][0])

    def __unicode__(self):
        return u"{}".format(smart_text(self.pk))

    def __str__(self):
        return self.__unicode__()

    def __fields__(self, info):
        fields = []
        fields.append(('pos', _("POS")))
        fields.append(('enable', _("Enable")))
        fields = get_external_method(POSOperator, '__fields_posoperator__', info, fields)
        return fields


# operators
class GenPOSOperator(GenInterface, ABSTRACT_GenPOSOperator):  # META: Abstract class
    pos_operator = models.OneToOneField(POSOperator, related_name='external', verbose_name=_("POS Operator"), null=True, on_delete=models.SET_NULL, blank=True)

    class Meta(GenInterface.Meta, ABSTRACT_GenPOSOperator.Meta):
        abstract = True

    @classmethod
    def permissions(cls):
        group = 'POSOperator'
        perms = []
        print(cls.posoperator.field.related_model)

        return None

        # print({group: {'gperm': None, 'dperm': perms, 'model': None},})

{'fraction_non_alphanumeric': 0.13723608445297505, 'fraction_numerical': 0.002630269424895145, 'mean_word_length': 3.0864197530864197, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 3, 'https://': 0, 'lorem ipsum': 0, 'www.': 4, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1514775', 'n_tokens_mistral': 8819, 'n_tokens_neox': 8581, 'n_words': 2033}