Django Many to Many and admin

I have a django ap which has a rather complicated model setup. I created a layered composition to create a hierarchical model. All relations are with each other, so I could use inheritance, but I decided not to make it profitable to have a composition of objects for my models, which means that I can do things like

product.outerframe.top.cost

which do the complex calculations that I have to transform are much better organized.

However, this model allows you to use the django admin. I basically have a through table, i.e. An external table is just a bunch of foreign keys for other tables (with a unique constraint for each). I ended up using the add_view () and change_view () methods of ModelAdmin, which is pretty complicated.

Is there an easier way to deal with many or many tables when using the django admin?

enter image description here

The tables are arranged as follows:

Product> outer frame, inner frame, glass, other

outer frame> top, bottom, side, etc.

innerframe> top, bottom, side, etc.

glass> glass_type etc.

other> accessories, etc.

Here are my models:

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

Impurities:

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

and hair extension by the administrator!

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)
+4
source share
1 answer

add_view, "". . , , ; .

, . -, UI , .

- , , . ModelForms InlineModelFormsets, , , , , ; ( ), .

, " " ( , ), , - :

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

+1

Source: https://habr.com/ru/post/1548045/


All Articles