2009-07-13 13 views
79

Wenn Sie ein Modellfeld mit einer Auswahloption haben, neigen Sie dazu, einige magische Werte mit lesbaren Namen assoziiert zu haben. Gibt es in Django eine bequeme Möglichkeit, diese Felder mit dem für Menschen lesbaren Namen anstatt mit dem Wert zu versehen?Setzen Sie Django IntegerField nach Auswahl = ... Name

Betrachten Sie dieses Modell:

class Thing(models.Model): 
    PRIORITIES = (
    (0, 'Low'), 
    (1, 'Normal'), 
    (2, 'High'), 
) 

    priority = models.IntegerField(default=0, choices=PRIORITIES) 

Irgendwann wir eine Sache Instanz haben, und wir wollen seine Priorität setzen. Natürlich könnten Sie das tun,

thing.priority = 1 

Aber das zwingt Sie, die Value-Name-Zuordnung von PRIORITIES zu merken. Dies funktioniert nicht:

thing.priority = 'Normal' # Throws ValueError on .save() 

Zur Zeit habe ich diese dumme Abhilfe:

thing.priority = dict((key,value) for (value,key) in Thing.PRIORITIES)['Normal'] 

aber das ist klobig. Angesichts der Häufigkeit dieses Szenarios fragte ich mich, ob jemand eine bessere Lösung hätte. Gibt es eine Feldmethode zum Festlegen von Feldern nach Wahlnamen, die ich völlig übersehen habe?

Antwort

130

Do als seen here. Dann können Sie ein Wort verwenden, das die richtige ganze Zahl darstellt.

Wie so:

LOW = 0 
NORMAL = 1 
HIGH = 2 
STATUS_CHOICES = (
    (LOW, 'Low'), 
    (NORMAL, 'Normal'), 
    (HIGH, 'High'), 
) 

Dann sind sie immer noch ganze Zahlen im DB.

Verwendung wäre thing.priority = Thing.NORMAL

+1

Das ist eine schön detaillierte Blog-Posting zu diesem Thema. Schwer zu finden bei Google auch so danke. –

+1

FWIW, wenn Sie es aus einem Literal String (vielleicht aus einem Formular, Benutzereingabe oder ähnliches) setzen Sie können dann tun Sie einfach: thing.priority = getattr (thing, strvalue.upper()). – mrooney

+1

Wirklich wie die Kapselung Abschnitt auf dem Blog. –

1

Ersetzen Sie einfach Ihre Nummern durch die menschenlesbaren Werte, die Sie möchten. Als solcher:

PRIORITIES = (
('LOW', 'Low'), 
('NORMAL', 'Normal'), 
('HIGH', 'High'), 
) 

Dies macht es lesbar, aber Sie müssten Ihre eigene Bestellung definieren.

7

ich wahrscheinlich das Reverse-Lookup dict ein für alle Mal würde einrichten, aber wenn ich nicht hätte, würde ich nur verwenden:

thing.priority = next(value for value, name in Thing.PRIORITIES 
         if name=='Normal') 

, die auf der Fliege als der Bau nur die dict einfacher scheint um es wieder wegzuwerfen ;-).

+0

Ja, das Diktat zu werfen ist ein bisschen albern, jetzt, wo du es sagst. :) –

4

Hier ist ein Feldtyp ich vor ein paar Minuten geschrieben, dass ich denke, das tut, was Sie wollen. Sein Konstruktor benötigt ein Argument 'choices', das entweder ein Tupel von 2-Tupeln im selben Format wie die Auswahloption zu IntegerField sein kann, oder stattdessen eine einfache Liste von Namen (zB ChoiceField ('Low', 'Normal', 'Hoch'), Standard = 'Niedrig')). Die Klasse kümmert sich um das Mapping von String nach Int für Sie, Sie sehen nie den Int.

class ChoiceField(models.IntegerField): 
    def __init__(self, choices, **kwargs): 
     if not hasattr(choices[0],'__iter__'): 
      choices = zip(range(len(choices)), choices) 

     self.val2choice = dict(choices) 
     self.choice2val = dict((v,k) for k,v in choices) 

     kwargs['choices'] = choices 
     super(models.IntegerField, self).__init__(**kwargs) 

    def to_python(self, value): 
     return self.val2choice[value] 

    def get_db_prep_value(self, choice): 
     return self.choice2val[choice] 
+1

Das ist nicht schlecht Allan. Vielen Dank! –

3
class Sequence(object): 
    def __init__(self, func, *opts): 
     keys = func(len(opts)) 
     self.attrs = dict(zip([t[0] for t in opts], keys)) 
     self.choices = zip(keys, [t[1] for t in opts]) 
     self.labels = dict(self.choices) 
    def __getattr__(self, a): 
     return self.attrs[a] 
    def __getitem__(self, k): 
     return self.labels[k] 
    def __len__(self): 
     return len(self.choices) 
    def __iter__(self): 
     return iter(self.choices) 
    def __deepcopy__(self, memo): 
     return self 

class Enum(Sequence): 
    def __init__(self, *opts): 
     return super(Enum, self).__init__(range, *opts) 

class Flags(Sequence): 
    def __init__(self, *opts): 
     return super(Flags, self).__init__(lambda l: [1<<i for i in xrange(l)], *opts) 

es wie folgt verwendet:

Priorities = Enum(
    ('LOW', 'Low'), 
    ('NORMAL', 'Normal'), 
    ('HIGH', 'High') 
) 

priority = models.IntegerField(default=Priorities.LOW, choices=Priorities) 
2

ich die Konstante definiert Art und Weise zu schätzen wissen, aber ich glaube Enum Typ für diese Aufgabe weit am besten ist. Sie können Integer und eine Zeichenfolge für ein Element gleichzeitig darstellen, während der Code besser lesbar bleibt.

Enums wurden in Version 3.4 in Python eingeführt. Wenn Sie einen niedrigeren Wert verwenden (z. B. v2.x), können Sie ihn immer noch installieren, indem Sie backported package: pip install enum34 installieren.

# myapp/fields.py 
from enum import Enum  


class ChoiceEnum(Enum): 

    @classmethod 
    def choices(cls): 
     choices = list() 

     # Loop thru defined enums 
     for item in cls: 
      choices.append((item.value, item.name)) 

     # return as tuple 
     return tuple(choices) 

    def __str__(self): 
     return self.name 

    def __int__(self): 
     return self.value 


class Language(ChoiceEnum): 
    Python = 1 
    Ruby = 2 
    Java = 3 
    PHP = 4 
    Cpp = 5 

# Uh oh 
Language.Cpp._name_ = 'C++' 

Das ist so ziemlich alles. Sie können die ChoiceEnum erben Ihre eigenen Definitionen zu erstellen und sie in einer Modelldefinition verwenden wie:

from django.db import models 
from myapp.fields import Language 

class MyModel(models.Model): 
    language = models.IntegerField(choices=Language.choices(), default=int(Language.Python)) 
    # ... 

Querying ist Sahnehäubchen auf dem Kuchen, wie Sie sich vorstellen können:

MyModel.objects.filter(language=int(Language.Ruby)) 
# or if you don't prefer `__int__` method.. 
MyModel.objects.filter(language=Language.Ruby.value) 

sie in String, der auch leicht gemacht:

# Get the enum item 
lang = Language(some_instance.language) 

print(str(lang)) 
# or if you don't prefer `__str__` method.. 
print(lang.name) 

# Same as get_FOO_display 
lang.name == some_instance.get_language_display() 
1

Meine Antwort ist sehr spät und konnte offensichtlich heute-Django Experten scheinen, aber wer hier landet, ich entdeckte vor kurzem eine sehr elegante Lösung bringt durch django-Modell-utils: https://django-model-utils.readthedocs.io/en/latest/utilities.html#choices

Dieses Paket Sie Auswahl mit drei Lings definieren kann, wo:

  • Der erste Punkt ist der Datenbankwert
  • Der zweite Punkt ein Code lesbare Wert
  • Das dritte Element ist ein für Menschen lesbaren Wert

hier ist also, was Sie tun können:

from model_utils import Choices 

class Thing(models.Model): 
    PRIORITIES = Choices(
     (0, 'low', 'Low'), 
     (1, 'normal', 'Normal'), 
     (2, 'high', 'High'), 
    ) 

    priority = models.IntegerField(default=PRIORITIES.normal, choices=PRIORITIES) 

thing.priority = getattr(Thing.PRIORITIES.Normal) 

Auf diese Weise:

  • Sie können nützlich, um Ihre Menschen lesbaren Wert tatsächlich wählen Sie den Wert des Feldes (in meinem Fall ist es verwenden, weil ich wilden Inhalt und es in einem normalisierten Speicherung Schaben bin Art und Weise)
  • Ein sauberer Wert in der Datenbank gespeichert ist
  • Sie haben nichts nicht-DRY zu tun;)

Enjoy :)

+1

Völlig gültig, um django-model-utils zu bringen. Ein kleiner Vorschlag zur Erhöhung der Lesbarkeit; Verwenden Sie die Punktnotation auch auf dem Standardparameter: 'priority = models.IntegerField (default = PRIORITIES.Low, choices = PRIORITIES) '(die Prioritätszuweisung muss dazu natürlich innerhalb der Thing-Klasse eingerückt werden). Berücksichtigen Sie auch die Verwendung kleingeschriebener Wörter/Zeichen für den Python-Bezeichner, da Sie sich nicht auf eine Klasse beziehen, sondern stattdessen einen Parameter (Ihre Auswahl würde dann lauten: '(0, 'niedrig', 'niedrig'), und so weiter) . – Kim

+0

Danke @Kim, Antwort bearbeitet;) –

Verwandte Themen