2014-07-11 6 views
4

다소 복잡한 모델 설정이있는 django ap가 있습니다. 다중 계층 컴포지션을 사용하여 계층 모델을 만들었습니다. 모든 관계는 1-1, 그래서 사용 상속을 가질 수 있지만 내가 내 모델 객체 조성 혜택을 누릴 것이라고하지 너무 선택이 내가 복잡하게Django Many Many and admin

product.outerframe.top.cost 

등의 작업을 수행 할 수 있음을 의미 계산을 훨씬 더 잘 정리하여 수행해야합니다.

그러나이 모델 배열은 장고 관리를 까다롭게 사용합니다. 나는 기본적으로 through 테이블을 가지고 있습니다. 즉, outerframe 테이블은 다른 테이블에 대한 외래 키의 집합입니다 (각각에 고유 한 제약 조건이 있음). 나는 ModelAdmin의 add_view()와 change_view() 메소드를 oerriding하는 것을 끝내었다. 꽤 어렵다.

django 관리자를 사용할 때 많은 테이블을 다루는 더 쉬운 방법이 있습니까?

enter image description here

테이블이과 같이 배열된다 :

제품> outerframe, innerframe 유리 기타 등등

innerframe> 상단

outerframe> 위, 아래, 측면, 바닥, 측면 등

유리> 유리 타입 등

기타> 액세서리 등

다음

내 모델입니다 :

class Product(mixins.ProductVariables): 
    name = models.CharField(max_length=255) 
    sku = models.CharField(max_length=100, unique=True, db_index=True) 
    image = thumbnail.ImageField(upload_to='product_images', blank=True) 
    description = models.TextField(blank=True) 
    group = models.ForeignKey('ProductGroup', related_name='products', null=True) 
    hidden = models.BooleanField(default=False) 
    product_specific_mark_up = models.DecimalField(default=1.0, max_digits=5,decimal_places=2) 

    # Methods for totals 
    def total_material_cost(self, width, height, options): 
     return sum([ 
      self.outerframe.cost(width, height, options), 
      self.innerframe.cost(width, height, options), 
      self.glass.cost(width, height, options), 
      self.other.cost(width, height, options), 
     ]) 

    def total_labour_time(self, width, height, options): 
     return sum([ 
      self.outerframe.labour_time(width, height, options), 
      self.innerframe.labour_time(width, height, options), 
      self.glass.labour_time(width, height, options), 
      self.other.labour_time(width, height, options), 
     ]) 

    def total_co2_output(self, width, height, options): 
     return sum([ 
      self.outerframe.co2_output(width, height, options), 
      self.innerframe.co2_output(width, height, options), 
      self.glass.co2_output(width, height, options), 
      self.other.co2_output(width, height, options), 
     ]) 

    @property 
    def max_overall_width(self): 
     return 1000 

    @property 
    def max_overall_height(self): 
     return 1000 

    def __unicode__(self): 
     return self.name 


class OuterFrame(models.Model, mixins.GetFieldsMixin, mixins.GetRelatedClassesMixin): 
    top = models.OneToOneField(mixins.TopFrame) 
    bottom = models.OneToOneField(mixins.BottomFrame) 
    side = models.OneToOneField(mixins.SideFrame) 
    accessories = models.OneToOneField(mixins.Accessories) 
    flashing = models.OneToOneField(mixins.Flashing) 
    silicone = models.OneToOneField(mixins.Silicone) 

    product = models.OneToOneField(Product) 

    def cost(self, width, height, options): 
     #accessories_cost = (self.accessories.cost if options['accessories'] else 0) 
     #flashing_cost = (self.flashing.cost if options['flashing'] else 0) 
     #silicone_cost = (self.silicone.cost if options['silicone'] else 0) 
     return sum([ 
      self.top.cost * (width/1000), 
      self.bottom.cost * (width/1000), 
      self.side.cost * (width*2/1000), 
      #accessories_cost, 
      #flashing_cost, 
      #silicone_cost, 
     ]) 

    def labour_time(self, width, height, options): 
     return datetime.timedelta(minutes=100) 

    def CO2_output(self, width, height, options): 
     return 100 # some kg measurement 

    @classmethod 
    def get_fields(cls): 
     options = cls._meta 
     fields = {} 
     for field in options.fields: 
      if field.name == 'product': 
       continue 
      if isinstance(field, models.OneToOneField): 
       related_cls = field.rel.to 
       related_fields = fields_for_model(related_cls, fields=related_cls.get_fields()) 
       fields.update({ related_cls.__name__ + '_' + name:field for name, field in related_fields.iteritems() }) 
     return fields 



class InnerFrame(models.Model, mixins.GetFieldsMixin, mixins.GetRelatedClassesMixin): 
    top = models.OneToOneField(mixins.TopFrame) 
    bottom = models.OneToOneField(mixins.BottomFrame) 
    side = models.OneToOneField(mixins.SideFrame) 
    accessories = models.OneToOneField(mixins.Accessories) 

    product = models.OneToOneField(Product) 

    def cost(self, width, height, options): 
     #accessories_cost = (self.accessories.cost if options['accessories'] else 0) 
     print self.top.cost 
     return sum([ 
      self.top.cost * (width/1000), 
      self.bottom.cost * (width/1000), 
      self.side.cost * (width*2/1000), 
     # accessories_cost, 
     ]) 

    def labour_time(self, width, height, options): 
     return datetime.timedelta(minutes=100) 

    def CO2_output(self, width, height, options): 
     return 100 # some kg measurement 

class Glass(models.Model, mixins.GetRelatedClassesMixin): 
    glass_type_a = models.OneToOneField(mixins.GlassTypeA) 
    glass_type_b = models.OneToOneField(mixins.GlassTypeB) 
    enhanced = models.OneToOneField(mixins.Enhanced) 
    laminate = models.OneToOneField(mixins.Laminate) 
    low_iron = models.OneToOneField(mixins.LowIron) 
    privacy = models.OneToOneField(mixins.Privacy) 
    anti_slip = models.OneToOneField(mixins.AntiSlip) 
    heat_film_mirror = models.OneToOneField(mixins.HeatMirrorField) 
    posished_edges = models.OneToOneField(mixins.PolishedEdges) 

    product = models.OneToOneField(Product) 

    def cost(self, width, height, options): 
     return sum([ 
     ]) 

    def labour_time(self, width, height, options): 
     return datetime.timedelta(minutes=100) 

    def CO2_output(self, width, height, options): 
     return 100 # some kg measurement 

class Other(models.Model, mixins.GetRelatedClassesMixin): 
    num_packages = models.OneToOneField(mixins.NumberPackages) 

    product = models.OneToOneField(Product) 

    def cost(self, width, height, options): 
     return 100 

    def labour_time(self, width, height, options): 
     return datetime.timedelta(minutes=100) 

    def CO2_output(self, width, height, options): 
     return 100 # some kg measurement 

유지 mixin :

class TimeCostMixin(models.Model, GetFieldsMixin): 
    cost = models.DecimalField(default=0.0, max_digits=10, decimal_places=2) 
    time = models.TimeField(default=datetime.timedelta(0)) 
    class Meta: 
     abstract = True 

##### Frame ##### 
class FrameComponentMixin(TimeCostMixin): 
    external_width = models.IntegerField(default=0) 
    material_weight = models.DecimalField(default=0.0, max_digits=10,decimal_places=2) 
    u_value = models.DecimalField(default=0.0, max_digits=10,decimal_places=2) 
    class Meta: 
     abstract = True 


class TopFrame(FrameComponentMixin): 
    pass 


class BottomFrame(FrameComponentMixin): 
    pass 


class SideFrame(FrameComponentMixin): 
    pass 


class Accessories(TimeCostMixin): 
    material_weight = models.DecimalField(default=0.0,max_digits=10,decimal_places=2) 


class Flashing(TimeCostMixin): 
    pass 


class Silicone(TimeCostMixin): 
    labour_time = models.DecimalField(default=0.0, max_digits=10,decimal_places=2) 
################# 

##### Glass ##### 
class GlassTypeA(TimeCostMixin): 
    material_weight = models.DecimalField(default=0.0, max_digits=10,decimal_places=2) 
    u_value = models.DecimalField(default=0.0, max_digits=10,decimal_places=2) 

class GlassTypeB(TimeCostMixin): 
    material_weight = models.DecimalField(default=0.0, max_digits=10,decimal_places=2) 
    u_value = models.DecimalField(default=0.0, max_digits=10,decimal_places=2) 

class Enhanced(TimeCostMixin): 
    material_weight = models.DecimalField(default=0.0, max_digits=10,decimal_places=2) 

class Laminate(TimeCostMixin): 
    material_weight = models.DecimalField(default=0.0, max_digits=10,decimal_places=2) 

class LowIron(TimeCostMixin): 
    pass 

class Privacy(TimeCostMixin): 
    pass 

class AntiSlip(TimeCostMixin): 
    pass 

class HeatMirrorField(TimeCostMixin): 
    u_value = models.DecimalField(default=0.0, max_digits=10,decimal_places=2) 

class PolishedEdges(models.Model): 
    cost = models.DecimalField(default=0.0, max_digits=10, decimal_places=2) 
################## 

##### other ##### 
class NumberPackages(models.Model): 
    number_of_packages = models.IntegerField(default=0) 
################## 

헤어 당겨 관리!

class ProductAdmin(AdminImageMixin, admin.ModelAdmin): 
    inlines = [ProductDownloadInline, ProductConfigurationInline] 

    add_form_template = 'admin/products/add_form.html' 
    change_form_template = 'admin/products/add_form.html' 


    @csrf_protect_m 
    @transaction.atomic 
    def add_view(self, request, form_url='', extra_context=None): 
     extra_context = extra_context or {} 

     "The 'add' admin view for this model." 
     model = self.model 
     opts = model._meta 

     if not self.has_add_permission(request): 
      raise PermissionDenied 

     ModelForm = self.get_form(request) 
     formsets = [] 
     inline_instances = self.get_inline_instances(request, None) 
     if request.method == 'POST': 
      form = ModelForm(request.POST, request.FILES) 
      if form.is_valid(): 
       new_object = self.save_form(request, form, change=False) 
       form_validated = True 
      else: 
       form_validated = False 
       new_object = self.model() 
      prefixes = {} 
      for FormSet, inline in zip(self.get_formsets(request), inline_instances): 
       prefix = FormSet.get_default_prefix() 
       prefixes[prefix] = prefixes.get(prefix, 0) + 1 
       if prefixes[prefix] != 1 or not prefix: 
        prefix = "%s-%s" % (prefix, prefixes[prefix]) 
       formset = FormSet(data=request.POST, files=request.FILES, 
            instance=new_object, 
            save_as_new="_saveasnew" in request.POST, 
            prefix=prefix, queryset=inline.get_queryset(request)) 
       formsets.append(formset) 

      ##### 
      outer_frame_forms = [ 
       modelform_factory(cls)(request.POST, prefix='OuterFrame_'+cls.__name__) 
       for cls in models.OuterFrame.get_related_classes(exclude=['product']) 
      ] 
      inner_frame_forms = [ 
       modelform_factory(cls)(request.POST, prefix='InnerFrame'+cls.__name__) 
       for cls in models.InnerFrame.get_related_classes(exclude=['product']) 
      ] 
      glass_forms = [ 
       modelform_factory(cls)(request.POST, prefix='InnerFrame'+cls.__name__) 
       for cls in models.Glass.get_related_classes(exclude=['product']) 
      ] 
      other_forms = [ 
       modelform_factory(cls)(request.POST, prefix='InnerFrame'+cls.__name__) 
       for cls in models.Other.get_related_classes(exclude=['product']) 
      ] 
      ##### 

      if all_valid(formsets 
         +outer_frame_forms 
         +inner_frame_forms 
         +glass_forms 
         +other_forms 
         ) and form_validated: 
       self.save_model(request, new_object, form, False) 
       self.save_related(request, form, formsets, False) 
       self.log_addition(request, new_object) 

       ##### save object hierichy ##### 
       # inner frame 
       inner_frame = models.InnerFrame() 
       inner_frame.product = new_object 
       mapping = {f.rel.to:f.name for f in models.InnerFrame._meta.fields if f.name not in ['id','product']} 
       for f in inner_frame_forms: 
        obj = f.save() 
        setattr(inner_frame, mapping[obj.__class__], obj) 
       inner_frame.save() 
       # outer frame 
       outer_frame = models.OuterFrame() 
       outer_frame.product = new_object 
       mapping = {f.rel.to:f.name for f in models.OuterFrame._meta.fields if f.name not in ['id','product']} 
       for f in outer_frame_forms: 
        obj = f.save() 
        setattr(outer_frame, mapping[obj.__class__], obj) 
       outer_frame.save() 
       # glass 
       glass = models.Glass() 
       glass.product = new_object 
       mapping = {f.rel.to:f.name for f in models.Glass._meta.fields if f.name not in ['id','product']} 
       for f in glass_forms: 
        obj = f.save() 
        setattr(glass, mapping[obj.__class__], obj) 
       glass.save() 
       # other 
       other = models.Other() 
       other.product = new_object 
       mapping = {f.rel.to:f.name for f in models.Other._meta.fields if f.name not in ['id','product']} 
       for f in other_forms: 
        obj = f.save() 
        setattr(other, mapping[obj.__class__], obj) 
       other.save() 
       ################################# 

       return self.response_add(request, new_object) 
     else: 
      forms = SortedDict({}) 
      forms['Outer Frame Variables'] = { 
       cls.__name__: modelform_factory(cls)(prefix='OuterFrame_'+cls.__name__) 
       for cls in models.OuterFrame.get_related_classes(exclude=['product']) 
      } 
      forms['Inner Frame Variables'] = { 
       cls.__name__: modelform_factory(cls)(prefix='InnerFrame'+cls.__name__) 
       for cls in models.InnerFrame.get_related_classes(exclude=['product']) 
      } 
      forms['Glass Variables'] = { 
       cls.__name__: modelform_factory(cls)(prefix='InnerFrame'+cls.__name__) 
       for cls in models.Glass.get_related_classes(exclude=['product']) 
      } 
      forms['Other Variables'] = { 
       cls.__name__: modelform_factory(cls)(prefix='InnerFrame'+cls.__name__) 
       for cls in models.Other.get_related_classes(exclude=['product']) 
      } 
      extra_context['forms'] = forms 

      # Prepare the dict of initial data from the request. 
      # We have to special-case M2Ms as a list of comma-separated PKs. 
      initial = dict(request.GET.items()) 
      for k in initial: 
       try: 
        f = opts.get_field(k) 
       except models.FieldDoesNotExist: 
        continue 
       if isinstance(f, models.ManyToManyField): 
        initial[k] = initial[k].split(",") 
      form = ModelForm(initial=initial) 
      prefixes = {} 
      for FormSet, inline in zip(self.get_formsets(request), inline_instances): 
       prefix = FormSet.get_default_prefix() 
       prefixes[prefix] = prefixes.get(prefix, 0) + 1 
       if prefixes[prefix] != 1 or not prefix: 
        prefix = "%s-%s" % (prefix, prefixes[prefix]) 
       formset = FormSet(instance=self.model(), prefix=prefix, 
            queryset=inline.get_queryset(request)) 
       formsets.append(formset) 

     adminForm = helpers.AdminForm(form, list(self.get_fieldsets(request)), 
      self.get_prepopulated_fields(request), 
      self.get_readonly_fields(request), 
      model_admin=self) 
     media = self.media + adminForm.media 

     inline_admin_formsets = [] 
     for inline, formset in zip(inline_instances, formsets): 
      fieldsets = list(inline.get_fieldsets(request)) 
      readonly = list(inline.get_readonly_fields(request)) 
      prepopulated = dict(inline.get_prepopulated_fields(request)) 
      inline_admin_formset = helpers.InlineAdminFormSet(inline, formset, 
       fieldsets, prepopulated, readonly, model_admin=self) 
      inline_admin_formsets.append(inline_admin_formset) 
      media = media + inline_admin_formset.media 

     context = { 
      'title': _('Add %s') % force_text(opts.verbose_name), 
      'adminform': adminForm, 
      'is_popup': IS_POPUP_VAR in request.REQUEST, 
      'media': media, 
      'inline_admin_formsets': inline_admin_formsets, 
      'errors': helpers.AdminErrorList(form, formsets), 
      'app_label': opts.app_label, 
      'preserved_filters': self.get_preserved_filters(request), 
     } 
     context.update(extra_context or {}) 
     return self.render_change_form(request, context, form_url=form_url, add=True) 
+0

임의의 코드 샘플? – obayhan

+0

[django-mptt] (https://github.com/django-mptt/django-mptt/)에 익숙합니까? 그것은 계층 적 데이터로 작업하도록 설계되었습니다. 또한 당신이 찾고있는 것일 수도있는 [admin 패키지] (https://github.com/leukeleu/django-mptt-admin)가 있습니다 – yuvi

+0

저는 MPTT를 사용하여 파일 시스템을 관리하는 프로젝트를 진행했습니다. 트리 구조를 구성하는 테이블은 대체로 이기종이기 때문에이 경우는 약간 다릅니다. MPTT는 같은 유형의 객체 트리 계층 구조에 적합하도록 설계 되었습니까? – TimRich

답변

1

내가 완전히 긴 add_view 방법을 처리하지 않은,하지만 일반적인 질문에 대한 대답은 "아니오"단순히 관리자는 여러 계층의 이기종 계층 구조를 처리하는 좋은 방법을 제공하지 않습니다. 2 계층 계층은 인라인으로 잘 처리되므로 어느 한 계층의 개체를 편집 할 때 다음 계층의 관련 개체를 편리하게 관리 할 수 ​​있도록 쉽게 만들 수 있습니다. 그러나 그것 저쪽에 아무것도.

관리자에게 중첩 된 인라인 지원을 추가하는 데 수년간 열려있는 a ticket이 있었기 때문에이 상황을 처리하는 데 도움이됩니다. 그러나 까다로운 가장자리 케이스가 많고 UI를 이해하기 어렵게 만들기 때문에 패치는 아직 준비 상태에 도달하지 못했습니다.

데이터 모델의 복잡성은 일반적인 관리 인터페이스가 뛰어난 유용성으로 처리 할 수있는 수준을 넘어서는 것이며 사용자 정의 된 관리 인터페이스를 작성하는 것이 좋습니다. 주로 관리자는 ModelForms 및 InlineModelFormsets 위에 구축되므로 원하는대로 작동하는 자신 만의 빌드를 생각하는 것만 큼 어렵지는 않습니다. 관리를 많이 사용자 정의하는 것보다 더 쉽고 (더 나은 결과로)

내재적으로 액세스하는 방법을 즉시 알 수는 없으므로 (통해 테이블이 암시 적이더라도 자체 모델 클래스가 아닌 경우에도) 다 대다 테이블을 통해 관리 인라인을 사용할 수 있음을 언급해야합니다. 모델을 통해 생성 :

class MyM2MInline(admin.TabularInline): model = SomeModel.m2m_field.through