2012-06-22 14 views
6

mam następujące:Czy można użyć klucza naturalnego dla klucza GenericForeignKey w Django?

target_content_type = models.ForeignKey(ContentType, related_name='target_content_type') 
target_object_id = models.PositiveIntegerField() 
target = generic.GenericForeignKey('target_content_type', 'target_object_id') 

Chciałbym dumpdata --natural emitować naturalny klucz do tej relacji. czy to możliwe? Jeśli nie, to czy istnieje alternatywna strategia, która nie wiązałaby mnie z kluczem podstawowym celu?

+0

Jestem ciekawy, czy znajdziesz jakieś rozwiązanie tego problemu? Zrobiłem trochę wyszukiwania, ale nic pomocnego nie wyszło. – shanyu

+0

jeszcze nie, ale zaktualizuję to rozwiązaniem, jeśli znajdę jeden – Riz

+0

Czy mógłbyś rozwinąć swoje pytanie? Jakiś przykład. – Rohan

Odpowiedz

6

TL; DR - Obecnie nie ma rozsądny sposób robić to, krótki tworzenia niestandardowego Serializer/Deserializer parę.

Problem z modeli, które mają relacje rodzajowe Django jest to, że nie widzi target jako pole w ogóle, tylko target_content_type i target_object_id i próbuje serializacji i deserializacji je indywidualnie.

Klasy odpowiedzialne za szeregowanie i deserializację modeli Django znajdują się w modułach django.core.serializers.base i django.core.serializers.python. Wszystkie inne (xml, json i yaml) rozszerzają jeden z nich (i python rozciąga base). Serializacji pole jest zrobione tak (nieistotne linie, pominięte):

for obj in queryset: 
     for field in concrete_model._meta.local_fields: 
       if field.rel is None: 
         self.handle_field(obj, field) 
       else: 
         self.handle_fk_field(obj, field) 

Oto pierwsza komplikacja: klucz obcy ContentType manipulowaniu ok, z kluczami naturalnych jak się spodziewaliśmy. Ale PositiveIntegerField jest obsługiwane przez handle_field, który jest realizowany w ten sposób:

def handle_field(self, obj, field): 
    value = field._get_val_from_obj(obj) 
    # Protected types (i.e., primitives like None, numbers, dates, 
    # and Decimals) are passed through as is. All other values are 
    # converted to string first. 
    if is_protected_type(value): 
     self._current[field.name] = value 
    else: 
     self._current[field.name] = field.value_to_string(obj) 

to jedyna możliwość personalizacji tutaj (instacji PositiveIntegerField i definiowania custom value_to_string) będzie mieć żadnego wpływu, ponieważ serializer nie nazwać. Zmiana typu danych target_object_id na coś innego niż liczba całkowita prawdopodobnie przerwie wiele innych rzeczy, więc nie jest to opcja.

Mamy mógłby zdefiniować nasz zwyczaj handle_field emitować klucze naturalne w tym przypadku, ale potem przychodzi drugi komplikacja: the deserializacji odbywa się to tak:

for (field_name, field_value) in six.iteritems(d["fields"]): 
     field = Model._meta.get_field(field_name) 
     ... 
      data[field.name] = field.to_python(field_value) 

Nawet jeśli dostosował metodę to_python, to działa wyłącznie w kontekście obiektu. Nie ma problemu z używaniem liczb całkowitych, ponieważ będzie interpretowany jako klucz podstawowy modelu bez względu na model, który jest. Ale aby deserializować klucz naturalny, najpierw musimy wiedzieć, do którego modelu należy ten klucz, a informacja ta nie jest dostępna, chyba że mamy odnośnik do obiektu (a pole target_content_type zostało już deserializowane).

Jak widać, nie jest to niemożliwe zadanie - wspieranie naturalnych kluczy w ogólnych relacjach - ale osiągnięcie tego, że wiele rzeczy będzie musiało zostać zmienionych w kodowaniu serializacyjnym i deserializacyjnym.Kroki niezbędne, a następnie (jeśli ktoś czuje się na siłach) to:

  • Tworzenie niestandardowego Field rozciągający PositiveIntegerField, z metod kodujących/odczytu obiektu - wywołanie odwołuje modele natural_key i get_by_natural_key;
  • Zastąp koder serializatora handle_field, aby wywołać koder, jeśli jest obecny;
  • Wykonaj niestandardowy deserializator, który: 1) narzuca pewien porządek w polach, upewniając się, że typ zawartości jest deserializowany przed kluczem naturalnym; 2) wywołuje dekoder, przekazując nie tylko field_value, ale także odniesienie do dekodowanego ContentType.
0

Napisałem niestandardowy Serializer i Deserializer obsługujący GenericFK. Sprawdziłem to krótko i wydaje się, że wykonuję to zadanie.

To właśnie wymyśliłem:

import json 

from django.contrib.contenttypes.generic import GenericForeignKey 
from django.utils import six 
from django.core.serializers.json import Serializer as JSONSerializer 
from django.core.serializers.python import Deserializer as \ 
    PythonDeserializer, _get_model 
from django.core.serializers.base import DeserializationError 
import sys 


class Serializer(JSONSerializer): 

    def get_dump_object(self, obj): 
     dumped_object = super(CustomJSONSerializer, self).get_dump_object(obj) 
     if self.use_natural_keys and hasattr(obj, 'natural_key'): 
      dumped_object['pk'] = obj.natural_key() 
      # Check if there are any generic fk's in this obj 
      # and add a natural key to it which will be deserialized by a matching Deserializer. 
      for virtual_field in obj._meta.virtual_fields: 
       if type(virtual_field) == GenericForeignKey: 
        content_object = getattr(obj, virtual_field.name) 
        dumped_object['fields'][virtual_field.name + '_natural_key'] = content_object.natural_key() 
     return dumped_object 


def Deserializer(stream_or_string, **options): 
    """ 
    Deserialize a stream or string of JSON data. 
    """ 
    if not isinstance(stream_or_string, (bytes, six.string_types)): 
     stream_or_string = stream_or_string.read() 
    if isinstance(stream_or_string, bytes): 
     stream_or_string = stream_or_string.decode('utf-8') 
    try: 
     objects = json.loads(stream_or_string) 
     for obj in objects: 
      Model = _get_model(obj['model']) 
      if isinstance(obj['pk'], (tuple, list)): 
       o = Model.objects.get_by_natural_key(*obj['pk']) 
       obj['pk'] = o.pk 
       # If has generic fk's, find the generic object by natural key, and set it's 
       # pk according to it. 
       for virtual_field in Model._meta.virtual_fields: 
        if type(virtual_field) == GenericForeignKey: 
         natural_key_field_name = virtual_field.name + '_natural_key' 
         if natural_key_field_name in obj['fields']: 
          content_type = getattr(o, virtual_field.ct_field) 
          content_object_by_natural_key = content_type.model_class().\ 
          objects.get_by_natural_key(obj['fields'][natural_key_field_name][0]) 
          obj['fields'][virtual_field.fk_field] = content_object_by_natural_key.pk 
     for obj in PythonDeserializer(objects, **options): 
      yield obj 
    except GeneratorExit: 
     raise 
    except Exception as e: 
     # Map to deserializer error 
     six.reraise(DeserializationError, DeserializationError(e), sys.exc_info()[2]) 
Powiązane problemy