diff --git a/cosiap_api/dynamic_tables/filtros.py b/cosiap_api/dynamic_tables/filtros.py new file mode 100644 index 0000000000000000000000000000000000000000..8f4fe1626cd947e7d4729583d5f41f498b6df9fa --- /dev/null +++ b/cosiap_api/dynamic_tables/filtros.py @@ -0,0 +1,96 @@ +from django.db import models +from django.db.models import Q + +CLASE_CAMPOS_BUSQUEDA = {'foreignKey': models.ForeignKey, 'manyToOne': models.ManyToOneRel, 'manyToMany': models.ManyToManyField, 'oneToOne': models.OneToOneRel} + +def get_related_fields(field, relatedFieldType, prefix='' ): + ''' + funcion recursiva para generar los nombres de los campos y los campos relacionados + + Argumentos: + - field (Campo relacionado del cual se obtendran sus campos) + - relatedFieldType (Tipo de campo permitido a buscar, si son de otro tipo seran ignorados) + - prefix (Prefijo que determina el nombre del campo en relacion con el modelo raiz) + ''' + + #el campo es un campo con un modelo relacionado valido, se llama a get_model_fields() para obtener sus campos + if isinstance(field, relatedFieldType): + related_model = field.related_model + related_fields = get_model_fields(related_model, relatedFieldType, prefix+field.name+'__') + return related_fields + #es un campo ignorado, se retorna una lista de campos vacia + elif field.__class__ in CLASE_CAMPOS_BUSQUEDA.values(): + return [] + #es un campo comun sin un modelo relacionado, se retorna tal cual + else: + return [prefix + field.name] + + +def get_model_fields(model, relatedFieldType, prefix='' ): + ''' + Funcion recursiva que llama a get_related_fields() para obtener los campos de un modelo. + + Argumentos: + - model (Modelo del cual se obtendran los nombres de los campos) + - relatedFieldType (Tipo de campo permitido a buscar, si son de otro tipo seran ignorados) + - prefix (Prefijo que determina el nombre del campo en relacion con el modelo raiz) + ''' + + fields = [] + #por cada campo del modelo se llama a get_related_fields() para obtener los nombres de los campos + #y o los nombres de los campos relacionados y se añade a la lista de campos + for field in model._meta.get_fields(): + fields.extend(get_related_fields(field, relatedFieldType, prefix)) + #se retornan todos los campos encontrados + return fields + + +def FiltradoEnCamposQuerySet(queryset, filter_query, matchExacto=False, relatedFieldType=CLASE_CAMPOS_BUSQUEDA['foreignKey']): + ''' + Funcion para filtrar un queryset en base a un string de palabras clave a buscar en sus campos + + - queryset (Queryset a la que se le aplicara el filtro) + - filter_query (String con todos los argumentos o terminos del filtro + pueden tener ) + - matchExacto (Determina el modo en que se manejan las coincidencias de los argumentos, si es true la coincidencia es verdadera si el match es exactamente la misma string, si es facil la coincidencia es verdadera si el term del argumento es una subscring del valor del campo) + - relatedFieldType (Tipo de campo con modelos relacionados que se tendran en cuenta, los demas se omitiran) + ''' + + search_terms = filter_query.split() #separamos la filter query en terms + model = queryset.model #obtenemos el modelo raiz del queryset + fields = get_model_fields(model, relatedFieldType) # obtenemos todos los nombres de los campos + + + q_objects = Q() + exclude_objects = Q() + + for term in search_terms: + term_query = Q() + is_exclude = term.startswith('-') # Verifica si el término comienza con '-' + is_or = term.startswith('~') # Verifica si el término comienza con '~' para OR + term = term[1:] if is_exclude or is_or else term + + for field in fields: + if ':' in term: #manejamos el tipo de argumento donde señala en que campo comparar + campo, valor = term.split(':', 1) + if campo == 'nombre' and ('nombre' == field or 'solicitante__nombre' in field): + term_query |= Q(**{f'{field}__icontains': valor}) + elif not matchExacto and campo != 'nombre' and campo in field: + term_query |= Q(**{f'{field}__icontains': valor}) + elif campo != 'nombre' and campo == field: + term_query |= Q(**{f'{field}__exact': valor}) + else: + term_query |= Q(**{f'{field}__icontains': term}) + + if is_exclude: # Agrega la condición al objeto de exclusión o al objeto de inclusión + exclude_objects &= term_query + elif is_or: # Realiza un OR con los términos de búsqueda + q_objects |= term_query + else: + q_objects &= term_query + + if search_terms and not q_objects: + queryset = model.objects.none() + else: + queryset = queryset.filter(q_objects).exclude(exclude_objects) + return queryset \ No newline at end of file