Search code examples
pythondjangodjango-admindjango-simple-history

django-simple-history How display related fields in the admin panel?


I use django-simple-history.I keep the historical table of the product and the price, the price is associated with the product and an inlane is added to the admin panelю I want to display in the admin panel in the product history records of the stories of related models (price). How can I do this? And that the fields changed would be displayed my model

class Product(models.Model):
    article = models.PositiveIntegerField()
    history = HistoricalRecords()

class Price(models.Model):
    prod = models.OneToOneField(
        Product,)
    price_supplier = models.FloatField()
    history = HistoricalRecords()

my admin

class PriceInline(admin.TabularInline):
    model = Price

class ProductAdmin(SimpleHistoryAdmin):
    inlines = [
        PriceInline,]

admin.site.register(Product, ProductAdmin)

enter image description here enter image description here

I tried to set it up via history_view() and get_history_queryset() to receive objects of another model and I received them, but I do not understand how to embed them in the render so that both changes in the product model and changes in the price model would be reflected, and at the same time the fields that changed were corresponding to their models. or is there another method to achieve this result


Solution

  • I found a solution, it's a bit of a kludge. Since we will not display the app price separately in the admin panel (it only participates in the inline of the product), we can change the history_view() function from django-simple-history. I will add an image model to the example to show how to use this approach with both one-to-one and one-to-many fields. At the same time, I disabled the ability to return the old value from history and for the template I changed what I displayed in the first column as a link because the links need to be configured separately. Now in the first column I just have the names of the field type.

    Now my code looks like this:

    model.py

        class Product(models.Model):
            article = models.PositiveIntegerField()
            history = HistoricalRecords()
    
        class Price(models.Model):
            prod = models.OneToOneField(Product,)
            price_supplier = models.FloatField()
            history = HistoricalRecords()
    
        class ProductImage(models.Model):
            product = models.ForeignKey(Product,)
            photo = models.ImageField("Изображение", upload_to=get_file_path_add)
            history = HistoricalRecords()
    

    admin.py

        class PriceInline(admin.TabularInline):
            model = Price
    
        class ProductImageInline(admin.TabularInline):
            model = ProductImage
    
        class PriceAdmin(SimpleHistoryAdmin):
            model = Price
    
            def history_view(self, request, object_id, extra_context=None):
                """The 'history' admin view for this model."""
    
                model = self.model
                opts = model._meta
                pk_name = opts.pk.attname
                history = getattr(model, model._meta.simple_history_manager_attribute)
    
                historical_records = PriceAdmin.get_history_queryset( PriceAdmin, 
                 request, history, pk_name, object_id )
    
                history_list_display = 
                 PriceAdmin.get_history_list_display(PriceAdmin,request)
    
                # Set attribute on each historical record from admin methods
                for history_list_entry in history_list_display:
                    value_for_entry = getattr(self, history_list_entry, None)
                    if value_for_entry and callable(value_for_entry):
                        for record in historical_records:
                            setattr(record, history_list_entry, 
                            value_for_entry(record))
    
                PriceAdmin.set_history_delta_changes(PriceAdmin, request, 
                historical_records)
    
                return historical_records
    
            def set_history_delta_changes(
                self,
                request,
                historical_records,
                foreign_keys_are_objs=True,
            ):
                previous = None
                for current in historical_records:
                    if previous is None:
                        previous = current
                        continue
                    # Related objects should have been prefetched in `get_history_queryset()`
                delta = previous.diff_against(
                    current, foreign_keys_are_objs=foreign_keys_are_objs
                )
                helper = PriceAdmin.get_historical_record_context_helper(
                    PriceAdmin, request, previous
                )
                previous.history_delta_changes = helper.context_for_delta_changes(delta)
    
                previous = current
    
        class ProductImageAdmin(SimpleHistoryAdmin):
            model = ProductImage
            
            def history_view(self, request, object_id, extra_context=None):
                """The 'history' admin view for this model."""
                model = self.model
                opts = model._meta
                pk_name = opts.pk.attname
                history = getattr(model, model._meta.simple_history_manager_attribute)
    
                historical_records = ProductImageAdmin.get_history_queryset(
                ProductImageAdmin, request, history, pk_name, object_id
            )
    
                history_list_display = ProductImageAdmin.get_history_list_display(
                ProductImageAdmin, request
            )
                # Set attribute on each historical record from admin methods
                for history_list_entry in history_list_display:
                    value_for_entry = getattr(self, history_list_entry, None)
                    if value_for_entry and callable(value_for_entry):
                         for record in historical_records:
                            setattr(record, history_list_entry, value_for_entry(record))
    
                ProductImageAdmin.set_history_delta_changes(
                ProductImageAdmin, request, historical_records
            )
    
                 return historical_records
    
             def set_history_delta_changes(
                self,
                request,
                historical_records,
                foreign_keys_are_objs=True,
        ):
                previous = None
                for current in historical_records:
                    if previous is None:
                        previous = current
                        continue
                    # Related objects should have been prefetched in `get_history_queryset()`
                    delta = previous.diff_against(
                    current, foreign_keys_are_objs=foreign_keys_are_objs
                )
                    helper = ProductImageAdmin.get_historical_record_context_helper(
                    ProductImageAdmin, request, previous
                )
                    previous.history_delta_changes = helper.context_for_delta_changes(delta)
    
                    previous = current
    
        class ProductAdmin(SimpleHistoryAdmin):
            inlines = [
            PriceInline,
            ProductImageInline,
        ]
            def history_view(self, request, object_id, extra_context=None):
            """The 'history' admin view for this model."""
            request.current_app = self.admin_site.name
    
            model = self.model
            opts = model._meta
            app_label = opts.app_label
            pk_name = opts.pk.attname
            history = getattr(model, model._meta.simple_history_manager_attribute)
    
            object_id = unquote(object_id)
            price_id = Price.objects.get(prod=object_id)
            image_id = ProductImage.objects.filter(product=object_id)
            
            historical_records = self.get_history_queryset(
                request, history, pk_name, object_id
            )
        #**here we get historical_records in image and price**
            historical_records_image = []
            for item in image_id:
                item_list = ProductImageAdmin.history_view(
                    ProductImageAdmin, request, item.id, extra_context=None
                )
                if historical_records_image == []:
                    historical_records_image = item_list
                else:
                    historical_records_image = list(
                        chain(
                            historical_records_image,
                            item_list,
                        )
                    )
            historical_records_price = PriceAdmin.history_view(
                PriceAdmin, request, price_id.id, extra_context=None
            )
             history_list_display = self.get_history_list_display(request)
    
            # If no history was found, see whether this object even exists.
            try:
                obj = self.get_queryset(request).get(**{pk_name: object_id})
            except model.DoesNotExist:
                try:
                    obj = historical_records.latest("history_date").instance
                except historical_records.model.DoesNotExist:
                    raise http.Http404
    
            if not self.has_view_history_or_change_history_permission(request, obj):
                raise PermissionDenied
    
            # Set attribute on each historical record from admin methods
            for history_list_entry in history_list_display:
                value_for_entry = getattr(self, history_list_entry, None)
                if value_for_entry and callable(value_for_entry):
                    for record in historical_records:
                        setattr(record, history_list_entry, value_for_entry(record))
    
            self.set_history_delta_changes(request, historical_records)
             # HERE WE COLLECT A GENERAL LIST OF ALL RECORDS
             result_list = list(
                chain(
                    historical_records,
                    historical_records_price,
                    historical_records_image,
                )
            )
            # HERE WE SORT THEM ALL BY TIME
            def get_date(element):
                return element.history_date
    
            result_list_sorted = result_list.sort(key=get_date, reverse=True)
    
            content_type = self.content_type_model_cls.objects.get_for_model(
                get_user_model()
            )
            admin_user_view = "admin:{}_{}_change".format(
                content_type.app_label,
                content_type.model,
            )
            context = {
                "title": self.history_view_title(request, obj),
                "object_history_list_template": self.object_history_list_template,
                "historical_records": result_list,
                "module_name": capfirst(force_str(opts.verbose_name_plural)),
                "object": obj,
                "root_path": getattr(self.admin_site, "root_path", None),
                "app_label": app_label,
                "opts": opts,
                "admin_user_view": admin_user_view,
                "history_list_display": history_list_display,
                "revert_disabled": self.revert_disabled(request, obj),
            }
            context.update(self.admin_site.each_context(request))
    
            context.update(extra_context or {})
            extra_kwargs = {}
            return self.render_history_view(
                request, self.object_history_template, context, **extra_kwargs
            )
    
        admin.site.register(Product, ProductAdmin)