2012-06-22 9 views
6

tôi có như sau:Có thể sử dụng khóa tự nhiên cho GenericForeignKey trong Django không?

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') 

Tôi muốn dumpdata --natural phát ra một chìa khóa tự nhiên đối với mối quan hệ này. Điều này có thể không? Nếu không, có một chiến lược thay thế nào sẽ không buộc tôi vào khóa chính của mục tiêu không?

+0

Tôi tò mò, bạn đã tìm thấy bất kỳ giải pháp cho việc này? Tôi đã làm một số tìm kiếm nhưng không có gì hữu ích đã đưa ra. – shanyu

+0

chưa nhưng tôi sẽ cập nhật điều này với một giải pháp nếu tôi tìm thấy một – Riz

+0

Bạn có thể xây dựng câu hỏi của mình không? Một số ví dụ. – Rohan

Trả lời

6

TL; DR - Hiện tại không có cách nào để làm như vậy, thiếu tạo một cặp tùy chỉnh Serializer/Deserializer.

Vấn đề với các mô hình có quan hệ chung là Django không thấy target như một lĩnh vực nào cả, chỉ target_content_typetarget_object_id, và nó cố gắng serialize và deserialize chúng riêng rẽ.

Các lớp chịu trách nhiệm tuần tự hóa và deserializing mô hình Django nằm trong các mô-đun django.core.serializers.basedjango.core.serializers.python. Tất cả những người khác (xml, jsonyaml) mở rộng một trong hai (và python kéo dài base). Serialization lĩnh vực được thực hiện như thế này (đường không liên quan Bỏ qua phần):

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) 

Đây là biến chứng đầu tiên: chìa khóa nước ngoài ContentType được xử lý ok, với các phím tự nhiên như chúng ta mong đợi. Nhưng PositiveIntegerField được xử lý bởi handle_field, đó là thực hiện như thế này:

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) 

ví dụ: khả năng duy nhất để tùy biến ở đây (subclassing PositiveIntegerField và xác định một custom value_to_string) sẽ không có hiệu lực, kể từ khi serializer sẽ không gọi nó. Thay đổi kiểu dữ liệu của target_object_id thành một số khác không phải là một số nguyên có thể sẽ phá vỡ nhiều thứ khác, do đó, nó không phải là một tùy chọn.

Chúng tôi thể xác định tùy chỉnh của chúng tôi handle_field để phát ra các phím tự nhiên trong trường hợp này, nhưng sau đó đến các biến chứng thứ hai: deserialization được thực hiện như thế này:

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) 

Thậm chí nếu chúng ta tùy chỉnh phương pháp to_python, nó chỉ hoạt động trên field_value, ngoài ngữ cảnh của đối tượng. Nó không phải là một vấn đề khi sử dụng số nguyên, vì nó sẽ được hiểu là khóa chính của mô hình bất kể mô hình là gì. Nhưng để deserialize một khóa tự nhiên, đầu tiên chúng ta cần phải biết mô hình đó là chìa khóa, và thông tin đó không có sẵn trừ khi chúng tôi có một tham chiếu đến đối tượng (và trường target_content_type đã được deserialized). Như bạn có thể thấy, nó không phải là một nhiệm vụ không thể - hỗ trợ các khóa tự nhiên trong quan hệ chung - nhưng để thực hiện điều đó, rất nhiều thứ sẽ cần phải được thay đổi trong mã tuần tự hóa và deserialization.Các bước cần thiết, sau đó (nếu có ai cảm thấy lên đến nhiệm vụ) là:

  • Tạo một tùy chỉnh Field mở rộng PositiveIntegerField, với các phương pháp mã hóa/giải mã một đối tượng - gọi các mô hình tham chiếu natural_keyget_by_natural_key;
  • Ghi đè số nối tiếp handle_field để gọi bộ mã hóa nếu có;
  • Triển khai trình giải mã tùy chỉnh mà: 1) áp đặt một số thứ tự trong các trường, đảm bảo loại nội dung được deserialized trước khóa tự nhiên; 2) gọi bộ giải mã, không chỉ qua số field_value mà còn tham chiếu đến mã được giải mã ContentType.
0

Tôi đã viết Trình tùy chỉnh và Trình xử lý tùy chỉnh hỗ trợ GenericFK. Kiểm tra nó một thời gian ngắn và có vẻ như để làm công việc.

Đây là những gì tôi đã đưa ra:

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])