product.outerframe.top.cost
这使得我必须进行复杂的计算,组织得更好.
但是,这种模式安排使得使用django admin很棘手.我基本上有一个直通表,即外框表只是一堆外键到其他表(每个表都有唯一约束).我最终了解了ModelAdmin的add_view()和change_view()方法,这非常困难.
使用django管理员时,是否有更简单的方法来处理多对多/通过表格?
表格的排列方式如下:
产品>外框,内框,玻璃,其他
外框>顶部,底部,侧面等
内框架>顶部,侧面等
玻璃> glass_type等
其他>配件等
这是我的模特:
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,options),self.innerframe.cost(width,self.glass.cost(width,self.other.cost(width,]) def total_labour_time(self,options): return sum([ self.outerframe.labour_time(width,self.innerframe.labour_time(width,self.glass.labour_time(width,self.other.labour_time(width,]) def total_co2_output(self,options): return sum([ self.outerframe.co2_output(width,self.innerframe.co2_output(width,self.glass.co2_output(width,self.other.co2_output(width,]) @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,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,options): return datetime.timedelta(minutes=100) def CO2_output(self,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.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,options): #accessories_cost = (self.accessories.cost if options['accessories'] else 0) print self.top.cost return sum([ self.top.cost * (width / 1000),# accessories_cost,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,options): return sum([ ]) def labour_time(self,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,options): return 100 def labour_time(self,options): return 100 # some kg measurement
混入:
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,decimal_places=2) u_value = models.DecimalField(default=0.0,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,decimal_places=2) class Flashing(TimeCostMixin): pass class Silicone(TimeCostMixin): labour_time = models.DecimalField(default=0.0,decimal_places=2) ################# ##### Glass ##### class GlasstypeA(TimeCostMixin): material_weight = models.DecimalField(default=0.0,decimal_places=2) class GlasstypeB(TimeCostMixin): material_weight = models.DecimalField(default=0.0,decimal_places=2) class Enhanced(TimeCostMixin): material_weight = models.DecimalField(default=0.0,decimal_places=2) class Laminate(TimeCostMixin): material_weight = models.DecimalField(default=0.0,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,decimal_places=2) class PolishedEdges(models.Model): cost = models.DecimalField(default=0.0,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,False) self.save_related(request,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,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,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,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,prefixes[prefix]) formset = FormSet(instance=self.model(),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)
解决方法
多年来一直有a ticket开放为管理员添加嵌套内联支持,这将有助于处理这种情况.但是有许多棘手的边缘情况,并且很难使UI易于理解,因此补丁从未达到提交就绪状态.
在某些时候,您的数据模型的复杂性超出了通用管理界面可以处理的良好可用性,并且您最好只编写自己的自定义管理界面.主要是管理员只是建立在ModelForms和InlineModelFormsets之上,所以它并不像你想象的那样难以构建你自己的工作方式;与尝试大量定制管理员相比,它通常更容易(并且具有更好的结果).
我还应该提一下,通过表可以使用admin inlines多对多(即使through表是隐式的,而不是它自己的模型类),因为如何访问隐式创建的直通模型并不是很明显:
class MyM2MInline(admin.TabularInline): model = SomeModel.m2m_field.through
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。