2009-07-14 9 views
7

Chcę utworzyć nowy typ pola dla modeli django, który jest w zasadzie ListOfStrings. Tak w kodzie modelu trzeba będzie następujący:W jaki sposób dziedziczysz i zastępujesz klasy modelu django, aby utworzyć listę listOfStringsField?

models.py:

from django.db import models 

class ListOfStringsField(???): 
    ??? 

class myDjangoModelClass(): 
    myName = models.CharField(max_length=64) 
    myFriends = ListOfStringsField() # 

other.py:

myclass = myDjangoModelClass() 
myclass.myName = "bob" 
myclass.myFriends = ["me", "myself", "and I"] 

myclass.save() 

id = myclass.id 

loadedmyclass = myDjangoModelClass.objects.filter(id__exact=id) 

myFriendsList = loadedclass.myFriends 
# myFriendsList is a list and should equal ["me", "myself", "and I"] 

Jak byś go o pisanie tego typu pola , z następującymi zastrzeżeniami?

  • Nie chcemy robić utworzyć pole, które właśnie crams wszystkie sznurki razem i oddziela je z tokena w jednej dziedzinie jak this. W niektórych przypadkach jest to dobre rozwiązanie, ale chcemy zachować normalizację danych ciągów, aby narzędzia inne niż django mogły wysyłać zapytania do danych.
  • To pole powinno automatycznie tworzyć tabele pomocnicze potrzebne do przechowywania danych ciągu.
  • W drugiej tabeli powinna znajdować się tylko jedna kopia każdego unikatowego ciągu. Jest to opcjonalne, ale byłoby miło mieć.

W kodzie Django wygląda na to, że chciałbym zrobić coś podobnego do tego, co robi ForeignKey, ale dokumentacja jest skąpa.

Prowadzi to do następujących pytań:

  • można to zrobić?
  • Czy zostało to zrobione (a jeśli tak, gdzie)?
  • Czy jest jakaś dokumentacja dotycząca Django, w jaki sposób rozszerzyć i zastąpić ich klasy modelu, w szczególności ich klasy relacji? Nie widziałem wiele dokumentacji na temat tego aspektu ich kodu, ale istnieje this.

Pochodzi z tego question.

+1

Jest to zdecydowanie możliwe, chociaż nie znam szczegółów. Mówię, przeczytaj ich listę mailingową i przejrzyj dokumenty, ale ostatecznie będziesz musiał przekopać się przez źródło Django. – AlbertoPL

+0

Jestem teraz w trakcie kopania przez źródło. Jeśli uda mi się wymyślić coś, co działa, postaram się opublikować to tutaj. – grieve

+0

Wysłałem moją próbę poniżej, która wydaje się działać. – grieve

Odpowiedz

5

To, co opisałem, brzmi bardzo podobnie do tagów.
Dlaczego więc nie używać django tagging?
Działa jak urok, można go zainstalować niezależnie od aplikacji, a jego interfejs API jest dość łatwy w użyciu.

+0

Ponadto, jeśli nie działa tak, jak chce OP, łatwo jest sprawdzić, jak się to robi i zmienić go na jego potrzeby. –

+0

Nice! Będę musiał przeanalizować to dalej. – grieve

6

Istnieje bardzo dobra dokumentacja dotycząca tworzenia niestandardowych pól here.

Jednak myślę, że to przemyślisz. Wygląda na to, że faktycznie potrzebujesz standardowego klucza obcego, ale z dodatkową możliwością pobierania wszystkich elementów jako pojedynczej listy. Tak najłatwiej byłoby po prostu użyć ForeignKey i zdefiniować metodę get_myfield_as_list modelu:

class Friends(model.Model): 
    name = models.CharField(max_length=100) 
    my_items = models.ForeignKey(MyModel) 

class MyModel(models.Model): 
    ... 

    def get_my_friends_as_list(self): 
     return ', '.join(self.friends_set.values_list('name', flat=True)) 

Teraz nazywając get_my_friends_as_list() na wystąpienie MyModel zwróci Ci listę ciągów, w miarę potrzeb.

+0

Pomyślałbym, że ForeignKey należy do klasy Friends Czy brakuje mi czegoś? Tak, prawdopodobnie nad tym myślę, ale jeśli mogę stworzyć pole, które chcę, myślę, że byłoby ogólnie użyteczne – grieve

+0

Tak, przepraszam, masz rację, zaktualizowałem kod: –

+0

Myślę, że FK również jest tutaj drogą +1 dla dodawania metody pobierania znajomych jako listy – googletorp

5

Też myślę, że idziesz o tym w niewłaściwy sposób. Próba uczynienia pola Django stworzeniem pomocniczej tabeli bazy danych jest prawie na pewno błędnym podejściem. Byłoby to bardzo trudne i prawdopodobnie wprowadziłoby w błąd twórców stron trzecich, jeśli próbujesz sprawić, by Twoje rozwiązanie było na ogół przydatne.

Jeśli próbujesz zapisać denormalizowaną kroplę danych w pojedynczej kolumnie, zastosowałbym podejście podobne do tego, z którym się łączyłeś, serializując strukturę danych Pythona i zapisując ją w polu TextField. Jeśli chcesz narzędzi innych niż Django, aby móc działać na danych następnie można serializacji do JSON (lub innym formacie, który posiada obsługę języka Wide):

from django.db import models 
from django.utils import simplejson 

class JSONDataField(models.TextField): 
    __metaclass__ = models.SubfieldBase 

    def to_python(self, value): 
     if value is None: 
      return None 
     if not isinstance(value, basestring): 
      return value 
     return simplejson.loads(value) 

    def get_db_prep_save(self, value): 
     if value is None: 
      return None 
     return simplejson.dumps(value) 

Jeśli chcesz po prostu deskryptor django Menedżer-like dzięki czemu można operować na liście łańcuchów powiązanych z modelem, można ręcznie utworzyć tabelę łączenia i użyć deskryptora do zarządzania relacją. Nie jest to dokładnie to, czego potrzebujesz, ale this code should get you started.

+0

Właśnie miałem okazję spojrzeć na link Myślę, że jest bliższy temu, co próbuję wykonać w sposób implementacyjny niż to, co obecnie mam. Dzięki! – grieve

+0

+1 za używanie tylko tego podejścia, jeśli jest ono przeznaczone do denormalizacji! W przeciwnym razie, normalizuj! – Soviut

+0

Pamiętaj, że get_db_prep_save pobiera teraz 4 argumenty (od django 1.5) – GreenAsJade

1

Dzięki za wszystkie odpowiedzi. Nawet jeśli nie użyłem twojej odpowiedzi bezpośrednio, przykłady i linki pozwoliły mi pójść we właściwym kierunku.

Nie jestem pewien, czy to jest gotowe do produkcji, ale wydaje się, że działa we wszystkich moich testach.

class ListValueDescriptor(object): 

    def __init__(self, lvd_parent, lvd_model_name, lvd_value_type, lvd_unique, **kwargs): 
     """ 
     This descriptor object acts like a django field, but it will accept 
     a list of values, instead a single value. 
     For example: 
      # define our model 
      class Person(models.Model): 
       name = models.CharField(max_length=120) 
       friends = ListValueDescriptor("Person", "Friend", "CharField", True, max_length=120) 

      # Later in the code we can do this 
      p = Person("John") 
      p.save() # we have to have an id 
      p.friends = ["Jerry", "Jimmy", "Jamail"] 
      ... 
      p = Person.objects.get(name="John") 
      friends = p.friends 
      # and now friends is a list. 
     lvd_parent - The name of our parent class 
     lvd_model_name - The name of our new model 
     lvd_value_type - The value type of the value in our new model 
         This has to be the name of one of the valid django 
         model field types such as 'CharField', 'FloatField', 
         or a valid custom field name. 
     lvd_unique - Set this to true if you want the values in the list to 
        be unique in the table they are stored in. For 
        example if you are storing a list of strings and 
        the strings are always "foo", "bar", and "baz", your 
        data table would only have those three strings listed in 
        it in the database. 
     kwargs - These are passed to the value field. 
     """ 
     self.related_set_name = lvd_model_name.lower() + "_set" 
     self.model_name = lvd_model_name 
     self.parent = lvd_parent 
     self.unique = lvd_unique 

     # only set this to true if they have not already set it. 
     # this helps speed up the searchs when unique is true. 
     kwargs['db_index'] = kwargs.get('db_index', True) 

     filter = ["lvd_parent", "lvd_model_name", "lvd_value_type", "lvd_unique"] 

     evalStr = """class %s (models.Model):\n""" % (self.model_name) 
     evalStr += """ value = models.%s(""" % (lvd_value_type) 
     evalStr += self._params_from_kwargs(filter, **kwargs) 
     evalStr += ")\n" 
     if self.unique: 
     evalStr += """ parent = models.ManyToManyField('%s')\n""" % (self.parent) 
     else: 
     evalStr += """ parent = models.ForeignKey('%s')\n""" % (self.parent) 
     evalStr += "\n" 
     evalStr += """self.innerClass = %s\n""" % (self.model_name) 

     print evalStr 

     exec (evalStr) # build the inner class 

    def __get__(self, instance, owner): 
     value_set = instance.__getattribute__(self.related_set_name) 
     l = [] 
     for x in value_set.all(): 
     l.append(x.value) 

     return l 

    def __set__(self, instance, values): 
     value_set = instance.__getattribute__(self.related_set_name) 
     for x in values: 
     value_set.add(self._get_or_create_value(x)) 

    def __delete__(self, instance): 
     pass # I should probably try and do something here. 


    def _get_or_create_value(self, x): 
     if self.unique: 
     # Try and find an existing value 
     try: 
      return self.innerClass.objects.get(value=x) 
     except django.core.exceptions.ObjectDoesNotExist: 
      pass 

     v = self.innerClass(value=x) 
     v.save() # we have to save to create the id. 
     return v 

    def _params_from_kwargs(self, filter, **kwargs): 
     """Given a dictionary of arguments, build a string which 
     represents it as a parameter list, and filter out any 
     keywords in filter.""" 
     params = "" 
     for key in kwargs: 
     if key not in filter: 
      value = kwargs[key] 
      params += "%s=%s, " % (key, value.__repr__()) 

     return params[:-2] # chop off the last ', ' 

class Person(models.Model): 
    name = models.CharField(max_length=120) 
    friends = ListValueDescriptor("Person", "Friend", "CharField", True, max_length=120) 

Ostatecznie myślę, że to nadal byłoby lepiej, gdyby zostały zepchnięte w głąb kodu django i pracował bardziej jak ManyToManyField lub ForeignKey.

+0

Zauważyłem w tej klasie, że musisz zapisać przed dodaniem, ponieważ identyfikator nie istnieje, dopóki go nie uratujesz. d będzie naprawiony/poprawiony, jeśli odziedziczony z Powiązanego pola i pola, ale nadal próbuję objąć głowę tym kodem. – grieve

+0

Po dalszych eksperymentach okazuje się, że działa, ale jest wyjątkowo kruchy, szczególnie w odniesieniu do przestrzeni nazw, musi żyć w models.py. Nadal będę nad tym pracował i mam nadzieję, że opracuję czystszą wersję. – grieve

Powiązane problemy