2012-10-10 21 views
31

Mam model Django, który opiera się na krotce. Zastanawiam się, jaka jest najlepsza praktyka odwoływania się do stałych w tej krotce dla mojego programu Django. Tutaj na przykład chciałbym określić "default=0" jako coś, co jest bardziej czytelne i nie wymaga komentowania. Jakieś sugestie?Najlepsza praktyka dla stałych Pythona i Django

Status = (
    (-1, 'Cancelled'), 
    (0, 'Requires attention'), 
    (1, 'Work in progress'), 
    (2, 'Complete'), 
) 

class Task(models.Model): 
    status = models.IntegerField(choices=Status, default=0) # Status is 'Requires attention' (0) by default. 

EDIT:

Jeśli to możliwe chciałbym uniknąć stosując szereg zupełnie. W jakiś sposób użycie łańcucha "Wymaga uwagi" byłoby bardziej czytelne.

+0

Czy znasz to interesujące podejście, które używa '__metaclass__'? http://tomforb.es/using-python-metaclasses-to-make-awesome-django-model-field-choices –

Odpowiedz

51

To jest dość powszechne do definiowania stałych dla wartości całkowitych następująco:

class Task(models.Model): 
    CANCELLED = -1 
    REQUIRES_ATTENTION = 0 
    WORK_IN_PROGRESS = 1 
    COMPLETE = 2 

    Status = (
     (CANCELLED, 'Cancelled'), 
     (REQUIRES_ATTENTION, 'Requires attention'), 
     (WORK_IN_PROGRESS, 'Work in progress'), 
     (COMPLETE, 'Complete'), 
    ) 

    status = models.IntegerField(choices=Status, default=REQUIRES_ATTENTION) 

Przesuwając stałych i Status wewnątrz klasy, zachować namespace czyszczenia modułu, a jako bonus można zwrócić się do Tasks.COMPLETE wszędzie tam, gdzie importujesz model Tasks.

+0

Daj również dostęp do reprezentacji ciągów z: 'def status_str (self): return self.Status [self.status] [1]' – Alveoli

+3

@ Model podstawowy alveoli django dostarcza funkcję "get_fieldname_display()" dla każdego pola w obrębie wzór. Zatem get_status_display() jest już dostępny – quin

+0

Nie sądzisz, że będzie wymagać dodatkowej pamięci dla obiektów tej klasy? Lepszym pomysłem byłoby zachować je oddzielnie i jeśli chcesz uzyskać do nich dostęp z obiektu, możesz zdefiniować funkcję, która może zwrócić to. – Anuj

1

Można używać słownika za niewielką poprawę jasności:

Status = { 
    -1: 'Cancelled', 
    0: 'Requires attention', 
    1: 'Work in progress', 
    2: 'Complete', 
} 

class Task(models.Model): 
    status = models.IntegerField(choices=Status.items(), default=Status[0]) 
+0

Myślę, że jest to bardzo podobne do zwykłej stałej liczbowej.Czy istnieje sposób na dodanie czytelnej dla człowieka informacji kontekstowej do tego, co w tym przypadku oznacza znaczenie "1"? Jak ktoś może powiedzieć, że 1 oznacza "Praca w toku"? –

2

Moje podejście:

class Task(models.Model): 
    STATUSES = { 'cancelled': 'Cancelled', 
       'requires attention': 'Requires attention', 
       'work in progress': 'Work in progress', 
       'complete': 'Complete' } 

    status = models.CharField(choices=STATUSES.items(), default='cancelled') 

To pozwala na pisanie wygodnych wyrażeń:

tasks = Task.objects.filter(status='complete') 

także pozwala nie tworzyć niepotrzebnych zmiennych globalnych.

Jeśli naprawdę chcesz używać całkowitą murawę:

class Task(models.Model): 

    class STATUS: 
     CANCELED, ATTENTION, WIP, COMPLETE = range(-1, 3) 
     choices = { 
     CANCELED: 'Cancelled', 
     ATTENTION: 'Requires attention', 
     WIP: 'Work in progress', 
     COMPLETE: 'Complete' 
     } 


    status = models.CharField(choices=STATUSES.choices.items(), default=STATUSES.CANCELED) 

oraz:

tasks = Task.objects.filter(status=Task.STATUSES.COMPLETE) 
+0

Myślę, że jest to bardzo podobne do posiadania stałej numerycznej. Czy istnieje sposób na dodanie czytelnej dla człowieka informacji kontekstowej do tego, co w tym przypadku oznacza znaczenie "1"? Jak ktoś może powiedzieć, że 1 oznacza "Praca w toku"? –

16
CANCELED, ATTENTION, WIP, COMPLETE = range(-1, 3) 
Status = (
    (CANCELED, 'Cancelled'), 
    (ATTENTION, 'Requires attention'), 
    (WIP, 'Work in progress'), 
    (COMPLETE, 'Complete'), 
) 

class Task(models.Model): 
    status = models.IntegerField(choices=Status, default=CANCELED) 


Należy pamiętać, że tak jak inni zauważyć, właściwym sposobem jest umieszczenie tych zmiennych wewnątrz klasy Model. Tak samo robi się oficjalny django example.

Jest tylko jeden powód, dla którego chcesz umieścić go poza obszarem nazw klas i to tylko wtedy, gdy semantyka jest równo dzielona z innymi modelami aplikacji. tj. nie można zdecydować, w którym konkretnym modelu należy się .

Chociaż nie wydaje się, że tak jest w tym konkretnym przypadku.

+1

Przewyższono, ale podoba mi się to lepiej, gdy stałe są przenoszone do klasy, jak w odpowiedzi Alasdaira. –

+0

@ChrisWesseling Całkowicie się zgadzam, po prostu nie chciałem odbiegać od przykładowego kodu OP. – rantanplan

+0

Mimo że jest używany tylko w modelu, czasami warto umieścić je poza klasą. Może w pliku stałych. Musiałem to zrobić, aby uniknąć importu okrężnego. Aby wywołać Task.CANCELED, muszę go zaimportować. Ponieważ jest on wywoływany w wielu miejscach, a modele zmieniają się bardzo szybko, istnieje szansa na cykliczne importowanie, jeśli zaimportujemy zadanie we wszystkich potrzebnych plikach. –

7

Można użyć numeru namedtuple, używając parametru niezmiennego dla stałej wydaje się pasować. ;-)

>>> from collections import namedtuple 
>>> Status = namedtuple('Status', ['CANCELLED', 'REQUIRES_ATTENTION', 'WORK_IN_PROGRESS', 'COMPLETE'])(*range(-1, 3)) 
>>> Status 
Status(CANCELLED=-1, REQUIRES_ATTENTION=0, WORK_IN_PROGRESS=1, COMPLETE=2) 
>>> Status.CANCELLED 
-1 
>>> Status[0] 
-1 

Korzystanie atrybuty na Task jako stałe jak w Alasdair's answer większy sens w tym przypadku, ale namedtuples są bardzo tanie substytuty dicts i przedmiotów, które nie zmieniają się. Szczególnie przydatny, jeśli chcesz mieć w pamięci wiele.Są jak zwykłe krotki z premią o opisowym dostępie do atrybutu.

+3

Jeśli potrzebujesz dalszych [przekonywujących] (http://pyvideo.org/video/367/pycon-2011--fun-with-python--39-s-newer-tools) '[11:35 - 26:00 ] ' – kreativitea

0

nie używam Django, ale zrobić coś jak następuje całkiem sporo pod piramidy i zakręcony ...

def setup_mapping(pairs): 
    mapping = {'id':{},'name':{}} 
    for (k,v) in pairs: 
     mapping['id'][k]= v 
     mapping['name'][v]= k 
    return mapping 

class ConstantsObject(object): 
    _pairs= None 
    mapping= None 

    @classmethod 
    def lookup_id(cls , id): 
     pass 

    @classmethod 
    def lookup_name(cls , name): 
     pass 

class StatusConstants(ConstantsObject): 
    CANCELLED = -1 
    REQUIRES_ATTENTION = 0 
    WORK_IN_PROGRESS = 1 
    COMPLETE = 2 

    _pairs= (
     (-1, 'Cancelled'), 
     (0, 'Requires attention'), 
     (1, 'Work in progress'), 
     (2, 'Complete'), 
    ) 
    mapping= setup_mapping(_pairs) 

Zatem istotą jest to:

  • Jest podstawowa klasa "stałe" i inna klasa dla każdego typu. klasa definiuje słowa kluczowe do wartości w ALLCAPS
  • Wrzucam też do klasy tekstowej _pairs. czemu? ponieważ może będę potrzebował zbudować z nimi tabele DB lub będę ich potrzebował dla komunikatów o błędach/statusie. Używam numerów, a nie nazwy zmiennej ALLCAPS jako osobistych preferencji.
  • zainicjować zmienną mapping klasy, które w zasadzie monkeypatches klasę przez prekompilacja kilka zmiennych w dict ponieważ ...
  • klasa jest wyprowadzona z klasy bazowej, która oferuje classmethod funkcjonalność, aby szukać wartości lub zrobić inne standardowe rzeczy, które często musisz zrobić ze stałymi.

Nie jest to podejście uniwersalne, ale ogólnie bardzo mi się to podoba. Możesz łatwo użyć dyktowania, aby zdefiniować pary, ustaw funkcję "mapowanie" na kilka innych atrybutów, na przykład dając krotki wartości pary jako k, v lub v, k lub dowolny dziwny format, który może być potrzebny.

mój kod może wtedy wygląda to tak:

status_id = sa.Column(sa.Integer, sa.ForeignKey("_status.id") , nullable=False , default=constants.StatusConstants.CANCELLED) 

status_name = constants.StatusConstants.lookup_id(status_id)  
status_name = constants.StatusConstants.mapping['id'][status_id] 

gdy trzeba użyć stałych w inny sposób, wystarczy dodać lub zmienić classmethods podstawy.

0

Czasami muszę utworzyć listę ogromnych opcji. Nie podoba mi się wpisywać jak małpa, więc raczej stworzyć función takiego:

def choices(labels): 
    labels = labels.strip().split('\n') 
    ids = range(1, len(labels)+1) 
    return zip(ids, labels) 

I używać tak:

my_choices = """ 
choice1 
choice2 
choice3 
""" 
MY_CHOICES = choices(my_choices) 
print(MY_CHOICES) # ((1, choice1), (2, choice2), (3, choice3)) 
6

Python 3.4+: Enum

Piszesz "Jeśli to możliwe, chciałbym zupełnie nie używać numeru". i rzeczywiście nazwana reprezentacja jest wyraźnie bardziej pytoniczna. Nagie stringi są jednak podatne na literówki.

Python 3.4 wprowadza moduł o nazwie enum zapewniając Enum i IntEnum pseudoclasses że pomóc w tej sytuacji. Dzięki niemu Twój przykład może działać w następujący sposób:

# in Python 3.4 or later: 
import enum 

class Status(enum.IntEnum): 
    Cancelled = -1, 
    Requires_attention = 0, 
    Work_in_progress = 1, 
    Complete = 2 

def choiceadapter(enumtype): 
    return ((item.value, item.name.replace('_', ' ')) for item in enumtype) 

class Task(models.Model): 
    status = models.IntegerField(choices=choiceadapter(Status), 
           default=Status.Requires_attention.value) 

i raz zespół Django podnosi Enum The choiceadapter nawet być wbudowane w Django.

Powiązane problemy