2010-08-03 18 views
5

Böyle bir model vardır:Django: birleştirme nesneleri

Ben birçok kaynaktan bunları aktarıyorum ve web sitemin kullanıcılar yeni Yerleri ekleyebilirsiniz yana
class Place(models.Model): 
    name = models.CharField(max_length=80, db_index=True) 
    city = models.ForeignKey(City) 
    address = models.CharField(max_length=255, db_index=True) 
    # and so on 

, bir onları birleştirmek için bir yol gerekir yönetici arayüzü

Place.objects.get(placename__name='St Paul\'s Cathedral', city=london) 

class Place(models.Model): 
    name = models.CharField(max_length=80, db_index=True) # canonical 
    city = models.ForeignKey(City) 
    address = models.CharField(max_length=255, db_index=True) 
    # and so on 

class PlaceName(models.Model): 
    name = models.CharField(max_length=80, db_index=True) 
    place = models.ForeignKey(Place) 

sorgu böyle ve benzeri birleştirme: Problem Böyle bir şey kullanmak alışığım vb adı onlar çok farklı şekillerde yazıldığından edilebilir beri çok güvenilir değil, olduğu Gördüğünüz gibi, diğer tüm modelleri yeni değerler ile FK to Place ile güncellemeliyim. Ama bu her yeni modeli bu listeye eklemem gerektiğinden çok iyi bir çözüm değil.

Silmeden önce tüm yabancı anahtarlar bazı nesnelere nasıl "basamaklı güncelleştirme" uygular?

Ya da belki burada gerçekten bu jenerik kodu, herkes intersted varsa/önlemek

cevap

6

birleştirme yapmak için başka çözümler vardır: kabul edilen yanıt açıklamalarda sağlanan pasajı dayanarak

def merge(self, request, queryset): 
    main = queryset[0] 
    tail = queryset[1:] 

    related = main._meta.get_all_related_objects() 

    valnames = dict() 
    for r in related: 
     valnames.setdefault(r.model, []).append(r.field.name) 

    for place in tail: 
     for model, field_names in valnames.iteritems(): 
      for field_name in field_names: 
       model.objects.filter(**{field_name: place}).update(**{field_name: main}) 

     place.delete() 

    self.message_user(request, "%s is merged with other places, now you can give it a canonical name." % main) 
+6

FWIW: http://djangosnippets.org/snippets/2283/ – dpn

+1

Pasaj artık beni tatmin görünmüyor, ForeignKey üzerinde başarısız olur. Artı işlem, atom lehine değer kaybetti. Artı iteritems() python3'te item() oldu. (son iki meseleyi çözmek kolaydı, ilk değil). – gabn88

+0

İlk sayıyı çözerken problemin django guardian'ın groupobjectpermissions ile olası olduğunu öğrendim. Yine de çözemedim :( – gabn88

2

Aşağıdakileri geliştirebildim. Bu kod GenericForeignKeys işlemez. Kullandığınız modelle ilgili bir sorun olduğunu düşündüğümden, kullanımlarına atıfta bulunmam.

Bu kod, atomik işlemlerin bulduğum diğer parçacıklarla tamamlanmasını engelleyen unique_together kısıtlamalarını ele alır. Kuşkusuz, onun uygulamada biraz hackish. Ayrıca django-audit-log kullanıyorum ve bu kayıtları değişiklikle birleştirmek istemiyorum. Ayrıca oluşturulan ve değiştirilen alanları uygun şekilde değiştirmek istiyorum. Bu kod Django 1.10 ve daha yeni Model _meta API ile çalışır.

from django.db import transaction 
from django.utils import timezone 
from django.db.models import Model 

def flatten(l, a=None): 
    """Flattens a list.""" 
    if a is None: 
     a = [] 
    for i in l: 
     if isinstance(i, Iterable) and type(i) != str: 
      flatten(i, a) 
     else: 
      a.append(i) 
    return a 


@transaction.atomic() 
def merge(primary_object, alias_objects=list()): 
    """ 
    Use this function to merge model objects (i.e. Users, Organizations, Polls, 
    etc.) and migrate all of the related fields from the alias objects to the 
    primary object. This does not look at GenericForeignKeys. 

    Usage: 
    from django.contrib.auth.models import User 
    primary_user = User.objects.get(email='[email protected]') 
    duplicate_user = User.objects.get(email='[email protected]') 
    merge_model_objects(primary_user, duplicate_user) 
    """ 
    if not isinstance(alias_objects, list): 
     alias_objects = [alias_objects] 

    # check that all aliases are the same class as primary one and that 
    # they are subclass of model 
    primary_class = primary_object.__class__ 

    if not issubclass(primary_class, Model): 
     raise TypeError('Only django.db.models.Model subclasses can be merged') 

    for alias_object in alias_objects: 
     if not isinstance(alias_object, primary_class): 
      raise TypeError('Only models of same class can be merged') 

    for alias_object in alias_objects: 
     if alias_object != primary_object: 
      for attr_name in dir(alias_object): 
       if 'auditlog' not in attr_name: 
        attr = getattr(alias_object, attr_name, None) 
        if attr and "RelatedManager" in type(attr).__name__: 
         if attr.exists(): 
          if type(attr).__name__ == "ManyRelatedManager": 
           for instance in attr.all(): 
            getattr(alias_object, attr_name).remove(instance) 
            getattr(primary_object, attr_name).add(instance) 
          else: 
           # do an update on the related model 
           # we have to stop ourselves from violating unique_together 
           field = attr.field.name 
           model = attr.model 
           unique = [f for f in flatten(model._meta.unique_together) if f != field] 
           updater = model.objects.filter(**{field: alias_object}) 
           if len(unique) == 1: 
            to_exclude = { 
             "%s__in" % unique[0]: model.objects.filter(
              **{field: primary_object} 
             ).values_list(unique[0], flat=True) 
            } 
           # Concat requires at least 2 arguments 
           elif len(unique) > 1: 
            casted = {"%s_casted" % f: Cast(f, TextField()) for f in unique} 
            to_exclude = { 
             'checksum__in': model.objects.filter(
              **{field: primary_object} 
             ).annotate(**casted).annotate(
              checksum=Concat(*casted.keys(), output_field=TextField()) 
             ).values_list('checksum', flat=True) 
            } 
            updater = updater.annotate(**casted).annotate(
             checksum=Concat(*casted.keys(), output_field=TextField()) 
            ) 
           else: 
            to_exclude = {} 

           # perform the update 
           updater.exclude(**to_exclude).update(**{field: primary_object}) 

           # delete the records that would have been duplicated 
           model.objects.filter(**{field: alias_object}).delete() 

      if hasattr(primary_object, "created"): 
       if alias_object.created and primary_object.created: 
        primary_object.created = min(alias_object.created, primary_object.created) 
       if primary_object.created: 
        if primary_object.created == alias_object.created: 
         primary_object.created_by = alias_object.created_by 
       primary_object.modified = timezone.now() 

      alias_object.delete() 

    primary_object.save() 
    return primary_object 
0

Django 1.10 üzerinde test edilmiştir. Umarım hizmet edebilir.

Django Uzantıları merge_model_instances yönetim komutu:

def merge(primary_object, alias_objects, model): 
"""Merge 2 or more objects from the same django model 
The alias objects will be deleted and all the references 
towards them will be replaced by references toward the 
primary object 
""" 
if not isinstance(alias_objects, list): 
    alias_objects = [alias_objects] 

if not isinstance(primary_object, model): 
    raise TypeError('Only %s instances can be merged' % model) 

for alias_object in alias_objects: 
    if not isinstance(alias_object, model): 
     raise TypeError('Only %s instances can be merged' % model) 

for alias_object in alias_objects: 
    # Get all the related Models and the corresponding field_name 
    related_models = [(o.related_model, o.field.name) for o in alias_object._meta.related_objects] 
    for (related_model, field_name) in related_models: 
     relType = related_model._meta.get_field(field_name).get_internal_type() 
     if relType == "ForeignKey": 
      qs = related_model.objects.filter(**{ field_name: alias_object }) 
      for obj in qs: 
       setattr(obj, field_name, primary_object) 
       obj.save() 
     elif relType == "ManyToManyField": 
      qs = related_model.objects.filter(**{ field_name: alias_object }) 
      for obj in qs: 
       mtmRel = getattr(obj, field_name) 
       mtmRel.remove(alias_object) 
       mtmRel.add(primary_object) 
    alias_object.delete() 
return True 
0

İki kütüphaneler şimdi modeli ilişkili modeller dahil fonksiyonlarını birleştirme kadar güncel olan vardır. Bu örnek daha kapsamlı bulundu

Django Super Deduper

İlgili konular