def importa_contactos(self, lista_rapida): parser = ParserCsv() try: estructura_archivo = parser.get_estructura_archivo(lista_rapida) cantidad_contactos = 0 if lista_rapida.cantidad_contactos: cantidad_contactos = lista_rapida.cantidad_contactos for lista_dato in estructura_archivo[1:]: cantidad_contactos += 1 ContactoListaRapida.objects.create( nombre=lista_dato[0], telefono=lista_dato[1], lista_rapida=lista_rapida, ) except OmlParserMaxRowError: lista_rapida.elimina_contactos() raise except OmlParserCsvImportacionError: lista_rapida.elimina_contactos() raise lista_rapida.cantidad_contactos = cantidad_contactos lista_rapida.save()
def importa_contactos(self, backlist): """ Segundo paso de la creación de una Backlist. 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. """ parser = ParserCsv() try: estructura_archivo = parser.get_estructura_archivo(backlist) cantidad_contactos = 0 if backlist.cantidad_contactos: cantidad_contactos = backlist.cantidad_contactos for lista_dato in estructura_archivo[1:]: cantidad_contactos += 1 ContactoBacklist.objects.create( telefono=lista_dato[0], back_list=backlist, ) except OmlParserMaxRowError: backlist.elimina_contactos() raise except OmlParserCsvImportacionError: backlist.elimina_contactos() raise backlist.cantidad_contactos = cantidad_contactos backlist.save()
def valida_contactos(self, base_datos_contacto): """ Validacion para ver si existe en la base de datos el contacto """ assert (base_datos_contacto.estado in (BaseDatosContacto.ESTADO_EN_DEFINICION, BaseDatosContacto.ESTADO_DEFINIDA_ACTUALIZADA)) parser = ParserCsv() try: estructura_archivo = parser.get_estructura_archivo( base_datos_contacto) cantidad_contactos = 0 for lista_dato in estructura_archivo[1:]: cantidad_contactos += 1 contacto = Contacto.objects.filter( # id_cliente=int(lista_dato[1]), bd_contacto=base_datos_contacto ) if len(contacto) > 0: raise (ContactoExistenteError(_("ya existe el contacto con el" " de id de cliente: {0}" " la base de datos ".format( int(lista_dato[1]))))) except OmlParserMaxRowError: base_datos_contacto.elimina_contactos() raise except OmlParserCsvImportacionError: base_datos_contacto.elimina_contactos() raise
def get(self, request, *args, **kwargs): self.object = self.get_object() estructura_archivo = self.obtiene_previsualizacion_archivo(self.object) if estructura_archivo: parser = ParserCsv() encoding = parser.detectar_encoding_csv(estructura_archivo) estructura_archivo_transformada = parser.visualizar_estructura_template( estructura_archivo, encoding) try: error_predictor = False error_predictor_encabezado = False predictor_metadata = PredictorMetadataService() metadata = predictor_metadata.inferir_metadata_desde_lineas( estructura_archivo, encoding) except NoSePuedeInferirMetadataError: initial_predecido_datos_extras = {} initial_predecido_encabezado = {} error_predictor = True except NoSePuedeInferirMetadataErrorEncabezado: initial_predecido_datos_extras = {} initial_predecido_encabezado = {} error_predictor_encabezado = True else: initial_predecido_datos_extras = dict([ ('datos-extras-{0}'.format(col), BaseDatosContacto.DATO_EXTRA_FECHA) for col in metadata.columnas_con_fecha ]) initial_predecido_datos_extras.update( dict([('datos-extras-{0}'.format(col), BaseDatosContacto.DATO_EXTRA_HORA) for col in metadata.columnas_con_hora])) initial_predecido_encabezado = { 'es_encabezado': metadata.primer_fila_es_encabezado } form_primer_linea_encabezado = PrimerLineaEncabezadoForm( initial=initial_predecido_encabezado) form_campos_telefonicos = CamposDeBaseDeDatosForm( nombres_campos=estructura_archivo[0]) return self.render_to_response( self.get_context_data( error_predictor_encabezado=error_predictor_encabezado, error_predictor=error_predictor, estructura_archivo=estructura_archivo_transformada, form_primer_linea_encabezado=form_primer_linea_encabezado, form_campos_telefonicos=form_campos_telefonicos)) return redirect(reverse('nueva_base_datos_contacto'))
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")
def __init__(self) -> None: self.parser = None self.legacy_parser = ParserCsv()
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()
def test_delimiter_incorrecto(self): parser = ParserCsv() with self.assertRaises(OmlParserCsvDelimiterError): planilla = self.get_test_resource("planilla-ejemplo-5.csv") parser._get_dialect(open(planilla, 'r'))
def test_cantidad_minima_de_filas(self): planilla = self.get_test_resource("planilla-ejemplo-4.csv") parser = ParserCsv() with self.assertRaises(OmlParserMinRowError): parser._get_dialect(open(planilla, 'r'))
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)) # FIXME: este metodo valida la consistencia de los metadatos, y # lanza una excepcion ante cualquier problema. OJO! Esto no implica # que los metadatos sean correctos y consistentes con los datos, # pero al menos validan la consistencia "interna" de los metadatos # metadata = base_datos_contacto.get_metadata() # metadata.validar_metadatos() # Antes que nada, borramos los contactos preexistentes # base_datos_contacto.elimina_contactos() parser = ParserCsv() ids_externos = base_datos_contacto.contactos.values_list('id_externo', flat=True) ids_externos = set(ids_externos) ids_nuevos_contactos = [] try: estructura_archivo = parser.get_estructura_archivo(base_datos_contacto) posicion_primer_telefono = estructura_archivo[0].index( str(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 OmlParserMaxRowError: base_datos_contacto.contactos.filter(id__in=ids_nuevos_contactos).delete() raise except OmlParserCsvImportacionError: base_datos_contacto.contactos.filter(id__in=ids_nuevos_contactos).delete() raise base_datos_contacto.cantidad_contactos = cantidad_contactos base_datos_contacto.save()
def form_valid(self, estructura_archivo, form_primer_linea_encabezado, form_campos_telefonicos): # columna_con_telefono = int(form_columna_telefono.cleaned_data.get( # 'telefono', None)) # cantidad_columnas = len(form_nombre_columnas.fields) cantidad_columnas = len(estructura_archivo[0]) # for numero_columna in range(cantidad_columnas): # dato_extra = form_datos_extras.cleaned_data.get( # 'datos-extras-{0}'.format(numero_columna), None) # if dato_extra == BaseDatosContacto.DATO_EXTRA_FECHA: # lista_columnas_fechas.append(numero_columna) # elif dato_extra == BaseDatosContacto.DATO_EXTRA_HORA: # lista_columnas_horas.append(numero_columna) # # nombre_columna = form_nombre_columnas.cleaned_data.get( # 'nombre-columna-{0}'.format(numero_columna), None) # # validador_nombre = ValidadorDeNombreDeCampoExtra() # if not validador_nombre.validar_nombre_de_columna(nombre_columna): # error = 'El nombre de la Columna{0} no es válido. Debe estar \ # en mayúscula y sin espacios. Por ejemplo: \ # TELEFONO_FIJO'.format(numero_columna) # # return self.form_invalid(estructura_archivo, # #form_columna_telefono, # #form_datos_extras, # #form_nombre_columnas, # form_primer_linea_encabezado, # error=error) # # lista_nombre_columnas.append(nombre_columna) # error = None # lista_columnas_encabezado = estructura_archivo[0] # if lista_columnas_encabezado[0] != 'telefono': # error = _("El nombre de la primera columna debe ser telefono") # if error: # return self.form_invalid(estructura_archivo, # form_primer_linea_encabezado, # form_campos_telefonicos, error=error) parser = ParserCsv() # Detecto el encondig de la base de datoss recientemente subida encoding = parser.detectar_encoding_csv(estructura_archivo) metadata = self.object.get_metadata() metadata.cantidad_de_columnas = cantidad_columnas # predictor_metadata = PredictorMetadataService() # columnas_con_telefonos = predictor_metadata.inferir_columnas_telefono( # estructura_archivo[1:], encoding) campos_telefonicos = form_campos_telefonicos.cleaned_data.get( 'campos_telefonicos') columnas_con_telefono = form_campos_telefonicos.columnas_de_telefonos metadata.columnas_con_telefono = columnas_con_telefono columna_id_externo = form_campos_telefonicos.columna_id_externo if columna_id_externo is not None: metadata.columna_id_externo = columna_id_externo metadata.nombres_de_columnas = [ value.decode(encoding) for value in estructura_archivo[0] ] es_encabezado = False if self.request.POST.get('es_encabezado', False): es_encabezado = True metadata.primer_fila_es_encabezado = es_encabezado metadata.save() creacion_base_datos = CreacionBaseDatosService() try: # creacion_base_datos.valida_contactos(self.object) creacion_base_datos.importa_contactos(self.object, campos_telefonicos, columna_id_externo) except CreacionBaseDatosServiceIdExternoError as e: message = _('<strong>Operación Errónea!</strong> ') +\ _('El archivo que seleccionó posee contactos con identificadores externos ' 'repetidos.<br> ' '<u>Línea Inválida:</u> {0}<br> <u>Contenido Línea:</u>' ' {1}<br><u>ID repetido:</u> {2}').format( e.numero_fila, e.fila, e.valor_celda) messages.add_message( self.request, messages.ERROR, message, ) return self.render_to_response( self.get_context_data( estructura_archivo=estructura_archivo, form_primer_linea_encabezado=form_primer_linea_encabezado)) except OmlParserCsvImportacionError as e: message = _('<strong>Operación Errónea!</strong> ') +\ _('El archivo que seleccionó posee registros inválidos.<br> ' '<u>Línea Inválida:</u> {0}<br> <u>Contenido Línea:</u>' '{1}<br><u>Contenido Inválido:</u> {2}').format( e.numero_fila, e.fila, e.valor_celda) messages.add_message( self.request, messages.ERROR, message, ) # FIXME: Ver bien que hacer acá. except ContactoExistenteError as e: message = _('<strong>Operación Errónea!</strong> ') +\ _('El archivo que seleccionó posee registros inválidos.<br> ' 'ERROR: {0}. Vuelva a cargar nuevamente la base de datos ' 'sin el contacto existente ').format(e) messages.add_message( self.request, messages.ERROR, message, ) return self.render_to_response( self.get_context_data( estructura_archivo=estructura_archivo, form_primer_linea_encabezado=form_primer_linea_encabezado, form_campos_telefonicos=form_campos_telefonicos)) except OmlParserMaxRowError: message = _('<strong>Operación Errónea!</strong> ') +\ _('El archivo que seleccionó posee más registros de los ' 'permitidos para ser importados.') messages.add_message( self.request, messages.ERROR, message, ) return redirect(reverse('lista_base_datos_contacto')) else: creacion_base_datos.define_base_dato_contacto(self.object) message = _('<strong>Operación Exitosa!</strong> ') +\ _('Se llevó a cabo con éxito la creación de ' 'la Base de Datos de Contactos.') messages.add_message( self.request, messages.SUCCESS, message, ) return redirect(self.get_success_url())