2010-09-08 9 views
8

He estado buscando una forma elegante de representar un campo de días laborables de selección múltiple (lunes, martes, miércoles ...) en un modelo de Django. Inicialmente estaba pensando en ir al campo entero usando matemáticas a nivel de bit pero no estoy seguro de si este sería el camino a seguir.Representación de un campo de selección múltiple para días de la semana en un modelo de Django

Este sería un campo de lectura mayoritaria. Me gustaría que el método Queryset fuera algo así como Entry.objects.get(weekdays__contains=MONDAY) Donde MONDAY sería una constante.

Quizás alguien podría encontrar una solución mejor? O tal vez alguien ha hecho algo similar y tiene algún código de ejemplo que podrían contribuir?

+0

Sin más información (tamaño del conjunto de datos, sobre todo leer frente a escribir en su mayoría, etc.) que va con bitfields se siente como la temida optimización prematura. –

+0

Adjuntaré información adicional a la pregunta. Gracias Sr. Rowell. –

+0

¿Ha considerado agregar una relación de muchos a muchos entre un modelo 'Weekday' y el modelo en cuestión? Sé que esto es un poco exagerado teniendo en cuenta que los días de la semana son fijos en número, pero harían el filtrado muy directo. –

Respuesta

14

Esta es una vieja pregunta, pero pensé que mostraría cómo se podía hacer razonablemente simplemente en Django.

Aquí es una clase de ayuda para la preparación de sus opciones:

class BitChoices(object): 
    def __init__(self, choices): 
    self._choices = [] 
    self._lookup = {} 
    for index, (key, val) in enumerate(choices): 
     index = 2**index 
     self._choices.append((index, val)) 
     self._lookup[key] = index 

    def __iter__(self): 
    return iter(self._choices) 

    def __len__(self): 
    return len(self._choices) 

    def __getattr__(self, attr): 
    try: 
     return self._lookup[attr] 
    except KeyError: 
     raise AttributeError(attr) 

    def get_selected_keys(self, selection): 
    """ Return a list of keys for the given selection """ 
    return [ k for k,b in self._lookup.iteritems() if b & selection] 

    def get_selected_values(self, selection): 
    """ Return a list of values for the given selection """ 
    return [ v for b,v in self._choices if b & selection] 

definir su modelo con un PositiveIntegerField, y las opciones que le gustaría:

WEEKDAYS = BitChoices((('mon', 'Monday'), ('tue', 'Tuesday'), ('wed', 'Wednesday'), 
       ('thu', 'Thursday'), ('fri', 'Friday'), ('sat', 'Saturday'), 
       ('sun', 'Sunday') 
      )) 

Esto significa que puede acceder a los valores como esto:

>>> print list(WEEKDAYS) 
[(1, 'Monday'), (2, 'Tuesday'), (4, 'Wednesday'), (8, 'Thursday'), (16, 'Friday'), (32, 'Saturday'), (64, 'Sunday')] 
>>> print WEEKDAYS.fri 
16 
>>> print WEEKDAYS.get_selected_values(52) 
['Wednesday', 'Friday', 'Saturday'] 

Defina ahora su modelo con PositiveIntegerField y estas opciones:

class Entry(models.Model): 
    weekdays = models.PositiveIntegerField(choices=WEEKDAYS) 

Y sus modelos están listos. Para consultas, lo que sigue es el truco:

Entry.objects.extra(where=["weekdays & %s"], params=[WEEKDAYS.fri]) 

Puede haber una manera de crear una subclase Q() objeto que cuidadosamente los paquetes de consultas, por lo que este aspecto:

Entry.objects.filter(HasBit('weekdays', WEEKDAYS.fri)) 

O incluso cortar a una F() subclase para crear algo como esto:

Entry.objects.filter(weekdays=HasBit(WEEKDAYS.fri)) 

Pero no tengo el tiempo para explorar que por el momento. .where funciona bien y se puede abstraer en una función queryset.

Una consideración final es que puede encenderse para crear un campo de modelo personalizado que convierta la máscara de bits en la base de datos a una lista o conjunto en Python. Luego puede usar un widget SelectMultiple (o CheckboxSelectMultiple) para permitir que el usuario seleccione sus valores en el administrador.

+0

Esta respuesta http: // stackoverflow.com/questions/19645227/django-create-multiselect-checkbox-input explica el asunto con respecto al widget – Yablargo

+0

Solo quiero decir gracias de nuevo, pude tropezar creando un artilugio en contra de tu clase, y funciona como un campeón. – Yablargo

+0

@Yablargo y autor: ¡Muchas gracias! También pude hacer un widget en contra de tu clase, pero estoy atascado en hacer que la función inversa (desde el valor (cadena) (digamos "12") hasta el dato de datos {(4, "miércoles"), (8, " jueves ")} para mostrar (y editar) los datos iniciales. ¿Podrían por favor ayudar aquí: http://stackoverflow.com/q/25575951/3849359? Gracias a un millón! – gabn88

Cuestiones relacionadas