def obtiene_previsualizacion_archivo(self, base_datos_contacto):
        """
        Instancia el servicio ParserCsv e intenta obtener un resumen de las
        primeras 3 lineas del csv.
        """

        try:
            parser = ParserCsv()
            estructura_archivo = parser.previsualiza_archivo(
                base_datos_contacto)

        except OmlParserCsvDelimiterError:
            message = _('<strong>Operación Errónea!</strong> ') +\
                _('No se pudo determinar el delimitador a ser utilizado '
                  'en el archivo csv. No se pudo llevar a cabo el procesamiento '
                  'de sus datos.')

            messages.add_message(
                self.request,
                messages.ERROR,
                message,
            )
        except OmlParserMinRowError:
            message = _('<strong>Operación Errónea!</strong> ') +\
                _('El archivo que seleccionó posee menos de 3 filas. '
                  'No se pudo llevar a cabo el procesamiento de sus datos.')

            messages.add_message(
                self.request,
                messages.ERROR,
                message,
            )
        except OmlParserOpenFileError:
            message = _('<strong>Operación Errónea!</strong> ') +\
                _('El archivo que seleccionó no pudo ser abierto para su procesamiento.')

            messages.add_message(
                self.request,
                messages.ERROR,
                message,
            )
        except Exception as e:
            message = _(
                'Error al procesar el archivo de base de contactos: {0}'.
                format(e))
            logger.error(message)
            messages.add_message(
                self.request,
                messages.ERROR,
                message,
            )
        else:
            return estructura_archivo
    def obtiene_previsualizacion_archivo(self, base_datos_contacto):
        """
        Instancia el servicio ParserCsv e intenta obtener un resumen de las
        primeras 3 lineas del csv.
        """

        # TODO: OML-1012
        #       Estas validaciones deberían realizarse antes de crear la Base de datos
        #       Sino queda una instancia creada inutilizable
        try:
            parser = ParserCsv()
            estructura_archivo = parser.previsualiza_archivo(
                base_datos_contacto)

        except OmlParserCsvDelimiterError:
            message = _('<strong>Operación Errónea!</strong> ') +\
                _('No se pudo determinar el delimitador a ser utilizado '
                  'en el archivo csv. No se pudo llevar a cabo el procesamiento '
                  'de sus datos.')

            messages.add_message(
                self.request,
                messages.ERROR,
                message,
            )
        except OmlParserMinRowError:
            message = _('<strong>Operación Errónea!</strong> ') +\
                _('El archivo que seleccionó posee menos de 3 filas. '
                  'No se pudo llevar a cabo el procesamiento de sus datos.')

            messages.add_message(
                self.request,
                messages.ERROR,
                message,
            )
        except OmlParserOpenFileError:
            message = _('<strong>Operación Errónea!</strong> ') +\
                _('El archivo que seleccionó no pudo ser abierto para su procesamiento.')

            messages.add_message(
                self.request,
                messages.ERROR,
                message,
            )
        except OmlParserRepeatedColumnsError as e:
            message = _('<strong>Operación Errónea!</strong> ') + e.message
            messages.add_message(
                self.request,
                messages.ERROR,
                message,
            )
        else:
            return estructura_archivo
    def test_con_demas_planillas_de_ejemplo(self):

        PLANILLAS = (
            "planilla-ejemplo-1.csv",
            "planilla-ejemplo-2.csv",
            "planilla-ejemplo-7-celdas-vacias.csv",
            "planilla-ejemplo-8-ultima-celda-vacia.csv",
        )

        for planilla in PLANILLAS:
            logger.debug("Procesando planilla %s", planilla)
            bd_contacto = BaseDatosContacto.objects.create(
                nombre="base-datos-contactos-{0}".format(planilla),
                archivo_importacion=self.copy_test_resource_to_mediaroot(
                    planilla),
                nombre_archivo_importacion=planilla)

            parser = ParserCsv()
            estructura_archivo = parser.previsualiza_archivo(bd_contacto)
            predictor_metadata = PredictorMetadataService()
            metadata_inferida = predictor_metadata.inferir_metadata_desde_lineas(
                estructura_archivo)

            metadata = bd_contacto.get_metadata()
            metadata._metadata = metadata_inferida._metadata
            metadata.nombres_de_columnas = [
                "COL{0}".format(num)
                for num in range(metadata.cantidad_de_columnas)
            ]
            metadata.save()

            creacion_base_datos_service = CreacionBaseDatosService()
            creacion_base_datos_service.importa_contactos(
                bd_contacto, ["Telefono"], None)
            creacion_base_datos_service.define_base_dato_contacto(bd_contacto)

            # ----- checks

            self.assertEquals(
                BaseDatosContacto.objects.get(pk=bd_contacto.id).estado,
                BaseDatosContacto.ESTADO_DEFINIDA,
                "La BD generada desde '{0}' NO ha quedado en estado ESTADO_DEFINIDA"
                "".format(planilla))

            self.assertTrue(
                Contacto.objects.filter(bd_contacto=bd_contacto.id).count() >
                0, "La BD generada desde '{0}' NO posee contactos".format(
                    planilla))
    def _obtiene_previsualizacion_archivo(self,
                                          lista_rapida,
                                          previsualizacion=True):
        """
        Instancia el servicio ParserCsv e intenta obtener un resumen de las
        primeras 3 lineas del csv.
        """
        try:
            parser = ParserCsv()
            estructura_archivo = parser.previsualiza_archivo(
                lista_rapida, previsualizacion)

        except OmlParserCsvDelimiterError:
            message = _(
                '<strong>Operación Errónea!</strong> '
                'No se pudo determinar el delimitador a ser utilizado '
                'en el archivo csv. No se pudo llevar a cabo el procesamiento '
                'de sus datos.')

            messages.add_message(
                self.request,
                messages.ERROR,
                message,
            )
        except OmlParserMinRowError:
            message = _(
                '<strong>Operación Errónea!</strong> '
                'El archivo que seleccionó posee menos de 3 filas. '
                'No se pudo llevar a cabo el procesamiento de sus datos.')

            messages.add_message(
                self.request,
                messages.ERROR,
                message,
            )
        except OmlParserOpenFileError:
            message = _(
                '<strong>Operación Errónea!</strong> '
                'El archivo que seleccionó no pudo ser abierto para su procesamiento.'
            )

            messages.add_message(
                self.request,
                messages.ERROR,
                message,
            )
        else:
            return estructura_archivo
    def test_inferir_metadata_correctamente(self):

        planilla = self.copy_test_resource_to_mediaroot("planilla-ejemplo-1.csv")
        nombre_archivo = "planilla-ejemplo-10.csv"
        base_test = BaseDatosContacto.objects.create(
            nombre="test", archivo_importacion=planilla,
            nombre_archivo_importacion=nombre_archivo, metadata="")

        parser = ParserCsv()
        estructura_archivo = parser.previsualiza_archivo(base_test)

        predictor_metadata = PredictorMetadataService()
        metadata = predictor_metadata.inferir_metadata_desde_lineas(estructura_archivo, "utf-8")

        validador_nombre = ValidadorDeNombreDeCampoExtra()
        for nombre_columna in metadata.nombres_de_columnas:
            self.assertTrue(validador_nombre.validar_nombre_de_columna(
                nombre_columna), "el nombre de columna es invalido")
    def test_con_planilla_ejemplo_3(self):
        bd_contacto = BaseDatosContacto.objects.create(
            nombre="base-datos-contactos",
            archivo_importacion=self.copy_test_resource_to_mediaroot(
                "planilla-ejemplo-3-headers-con-no-ascii-y-espacios.csv"),
            nombre_archivo_importacion=
            'planilla-ejemplo-3-headers-con-no-ascii-y-espacios.csv')

        parser = ParserCsv()
        estructura_archivo = parser.previsualiza_archivo(bd_contacto)
        predictor_metadata = PredictorMetadataService()
        metadata_inferida = predictor_metadata.inferir_metadata_desde_lineas(
            estructura_archivo)

        metadata = bd_contacto.get_metadata()
        metadata._metadata = metadata_inferida._metadata
        metadata.nombres_de_columnas = ["Telefono", "NOMBRE", "FECHA", "HORA"]
        metadata.columna_con_telefono = 0
        metadata.columnas_con_telefono = [0]
        metadata.save()

        creacion_base_datos_service = CreacionBaseDatosService()
        creacion_base_datos_service.importa_contactos(bd_contacto,
                                                      ["Telefono"], None)
        creacion_base_datos_service.define_base_dato_contacto(bd_contacto)

        # ----- checks

        self.assertEquals(
            BaseDatosContacto.objects.get(pk=bd_contacto.id).estado,
            BaseDatosContacto.ESTADO_DEFINIDA,
            "La BD no ha quedado en estado ESTADO_DEFINIDA")

        nros_telefono = [
            contacto.telefono
            for contacto in Contacto.objects.filter(bd_contacto=bd_contacto.id)
        ]

        self.assertEquals(len(nros_telefono), 3, "Deberia haber 3 contactos")

        self.assertEquals(
            set(nros_telefono),
            set(['354303459865', '111534509230', '283453013491']),
            "Deberia haber 3 contactos")
class BaseDatosContactoService(object):
    def __init__(self) -> None:
        self.parser = None
        self.legacy_parser = ParserCsv()

    def crear_bd_contactos(self, archivo, nombre_archivo, nombre_bd) -> int:
        model_base_contactos = BaseDatosContacto()
        if self._existe_bd_contactos(nombre_bd):
            raise (OmlError(
                _("Ya existe una base de datos de contactos con ese nombre")))
        self.parser = BaseDatosContactoArchivoCSVParser(
            nombre_archivo, archivo)

        if not self.parser.es_valida_extension(
        ) or not self.parser.es_valido_archivo():
            file_invalid_msg = _(
                "El archivo especificado para realizar la importación de "
                "contactos no es válido.")
            raise (OmlArchivoImportacionInvalidoError(file_invalid_msg))

        if not self.parser.headers_no_repetidos():
            raise OmlParserRepeatedColumnsError(
                _("El archivo a procesar tiene nombres de columnas "
                  "repetidos."))

        model_base_contactos.archivo_importacion = archivo
        model_base_contactos.nombre_archivo_importacion = nombre_archivo
        model_base_contactos.nombre = nombre_bd
        model_base_contactos.save()

        return model_base_contactos.id

    def define_base_datos_contactos(self, base_datos_contacto) -> None:
        base_datos_contacto.define()

    def obtiene_subconjunto_filas_archivo(self, base_datos_contacto) -> dict:
        return self.legacy_parser.previsualiza_archivo(base_datos_contacto)

    def inferir_metadata(self, estructura_archivo):
        predictor_metadata = PredictorMetadataService()
        return predictor_metadata.inferir_metadata_desde_lineas(
            estructura_archivo)

    def importa_contactos_desde_api(self, id, campos_telefonicos,
                                    columna_id_externo):
        bd_contactos = BaseDatosContacto.objects.obtener_en_actualizada_para_editar(
            id)
        self.importa_contactos(bd_contactos, campos_telefonicos,
                               columna_id_externo)

    def importa_contactos(self, base_datos_contacto, campos_telefonicos,
                          columna_id_externo):
        """
        Tercer paso de la creación de una BaseDatosContacto.
        Este método se encarga de generar los objectos Contacto por cada linea
        del archivo de importación especificado para la base de datos de
        contactos.
        """
        assert (base_datos_contacto.estado
                in (BaseDatosContacto.ESTADO_EN_DEFINICION,
                    BaseDatosContacto.ESTADO_DEFINIDA_ACTUALIZADA))

        ids_externos = base_datos_contacto.contactos.values_list('id_externo',
                                                                 flat=True)
        ids_externos = set(ids_externos)
        ids_nuevos_contactos = []
        try:
            estructura_archivo = self.legacy_parser.get_estructura_archivo(
                base_datos_contacto)
            posicion_primer_telefono = estructura_archivo[0].index(
                campos_telefonicos[0])
            cantidad_contactos = 0
            if base_datos_contacto.cantidad_contactos:
                cantidad_contactos = base_datos_contacto.cantidad_contactos
            numero_fila = 0
            for lista_dato in estructura_archivo[1:]:
                numero_fila += 1
                telefono, datos, id_externo = self._obtener_telefono_y_datos(
                    lista_dato, posicion_primer_telefono, columna_id_externo)
                cantidad_contactos += 1
                if id_externo is not None and id_externo != '':
                    # El id_externo no puede estar repetido
                    if id_externo in ids_externos:
                        base_datos_contacto.contactos.filter(
                            id__in=ids_nuevos_contactos).delete()
                        raise CreacionBaseDatosServiceIdExternoError(
                            numero_fila, columna_id_externo, lista_dato,
                            id_externo)
                    else:
                        ids_externos.add(id_externo)

                contacto = Contacto.objects.create(
                    telefono=telefono,
                    datos=datos,
                    bd_contacto=base_datos_contacto,
                    id_externo=id_externo)
                ids_nuevos_contactos.append(contacto.id)
        except CreacionBaseDatosServiceIdExternoError as e:
            raise e

        except OmlParserMaxRowError:
            base_datos_contacto.contactos.filter(
                id__in=ids_nuevos_contactos).delete()
            raise OmlError(_("Archivo excede máximo de filas permitidas"))

        except OmlParserCsvImportacionError:
            base_datos_contacto.contactos.filter(
                id__in=ids_nuevos_contactos).delete()
            raise OmlError(_("Error al parsear el archivo csv"))

        base_datos_contacto.cantidad_contactos = cantidad_contactos

        base_datos_contacto.save()

        self.define_base_datos_contactos(base_datos_contacto)

    def _existe_bd_contactos(self, nombre) -> bool:
        return BaseDatosContacto.objects.filter(nombre=nombre).exists()

    def _obtener_telefono_y_datos(self, lista_dato, posicion_primer_telefono,
                                  columna_id_externo) -> any:
        id_externo = None
        if len(lista_dato) > 1:
            item = []
            for i, valor in enumerate(lista_dato):
                if i == posicion_primer_telefono:
                    telefono = valor
                elif i == columna_id_externo:
                    id_externo = valor
                else:
                    item.append(valor)
        else:
            telefono = lista_dato[0]
            item = ['']

        datos = json.dumps(item)
        return telefono, datos, id_externo

    def remove_db(self, id):
        BaseDatosContacto.objects.filter(id=id).delete()