Django管理页面:通过多个模型选择而不是原始tex来定制id字典(JSONField)

2024-04-16 19:27:34 发布

您现在位置:Python中文网/ 问答频道 /正文

我有一个模型,其中一个字段是postgres.fields.JSONField。在

将要存储的Json有一个id的变量字典,它引用数据库中的其他项(可能的关系/属性)。在

请允许我更具体地说:

基本上,我正在尝试创建一个折扣系统,在这个系统中,某些产品可以得到一些折扣。JSON字段包含一些约束条件,用于知道哪些产品可以获得折扣。在

例如:

  • 如果我想对属于“饮料”类别的所有产品应用50%的折扣,并且“饮料”类别在数据库中有id 5,那么折扣记录将如下所示:

    discount_type='percent'
    discount='0.5'
    filter_by={
        'category': [5]
    }
    
  • 如果我想对“饮料”类别的所有产品(例如,CocaCola)给予20美元的折扣,那么filter_by字典将如下所示:

    discount_type='fixed amount'
    discount='20'
    filter_by={
        'category': [5],
        'manufacturer': [2]   # Assuming coca-cola is the Manufacturer 
                              # with id==2 in the 'Manufacturers'
                              # table of the database (NOTE: this is 
                              # needed since CocaCola manufactures
                              # products besides "Beverages")
    }
    
  • 如果我想对某个特定的产品打25%的折扣(比如对id3)的产品,字典应该是这样的:

    discount_type='percent'
    discount='0.25'
    filter_by={
        'id': [3]
    }
    

这个想法似乎对我的需求足够灵活,我很满意(到目前为止)。在


现在,问题出现在如何Discount模型的Django管理区域中输入这些值。在

如预期,filter_by字典呈现为文本字段,最初如下所示:

enter image description here

如果我想添加字段到它,我需要写我想要的确切的JSON。。。这意味着,如果我想对“Beverages”类别应用折扣,我需要找出该类别在数据库中有哪个ID,然后手动键入{"category": [5]},同时在键入':时非常小心,确保我不会错过][。。。在

塔亚特。。。好吧,那不是很有帮助。。。在

由于我将只按几个字段(categorymanufacturerproduct…)进行筛选,这实际上是数据库中其他元素的id列表,所以我想为每个我可以筛选的对象显示一个大的MultiSelect框,这样我就可以看到一个用户友好的列表,列出所有可以筛选的元素,选择一些,然后,当我点击“creatediscount”时,我会得到filter_by字典(我还不担心如何生成字典,因为我甚至不知道如何正确地呈现管理表单)。在

类似于Django Admin自动对我的产品类别所做的操作:

enter image description here

这真的,真的,很好:一个产品可以属于几个类别。为此,Django并排呈现两个<select multiple框,其中包含可用的类别和产品已经属于的类别。。。我可以添加/删除类别通过一个鼠标。。。真的,真的很好。但是Django可以这样做,因为它知道categoriesProduct模型中的ManyToMany关系。在

class Product(models.Model):
    parent = models.ForeignKey('self', null=True, blank=True)
    manufacturer = models.ForeignKey('Manufacturer')
    categories = models.ManyToManyField('Category',
                                         related_name='products', blank=True)

Discount模型的问题是没有ManyToMany字段来categorymanufacturer或{}。可怜的Django不知道Discount与所有这些事情都有关联:它只知道有一个Json字段。在

我真的希望能够在Django区域显示一组<select>,列出所有可能的过滤器(CategoryManufacturerID…),它们可以存储在filter_by字典中(其中一个条目带有双<select>表示{},其中一个条目用于Manufacturer,显示所有可用的制造商。。。等等)。但我真的,真的不知道该怎么做。在

我做了一系列尝试,使用Widgets,试图通过form,通过forms.ModelMultipleChoiceField来表示JSON字段(顺便说一下,这似乎是最接近我想要的,尽管还很遥远)。。。但我认为这是没有意义的,因为不是他接近我想要的。在

像往常一样,感谢您阅读这封巨大的电子邮件,并提前感谢您。任何提示都将非常感谢,即使只是一个您应该看看“this”


Tags: django模型id数据库jsonby字典产品
2条回答

所以。。。我很感激@阿方索·金但是创建一个全新的Django模型仅仅是为了达到“渲染”的目的,这对我来说有点过分了。拜托!别误会我的意思:这可能是一种“规范”的方法(我见过很多次推荐的方法),也许比所做的要好,但我想展示一下是如何解决我的特定问题的:

我查看了Django的源代码,特别是ManyToMany关系是如何在管理中显示的。如果你看看我上面最初的问题,我想知道Django在编辑一个产品时使用哪个类来显示类别(即“双列选择”,给它起一个我非常喜欢的名字)。原来它是一个django.forms.models.ModelMultipleChoiceField“经验丰富”,带有一个FilteredSelectMultiple小部件的提示。在

有了这些信息,我为我的Coupon类创建了一个自定义管理表单,手动添加了我想要显示的字段:

class CouponAdminForm(forms.ModelForm):
    brands = forms.ModelMultipleChoiceField(
                            queryset=Brand.objects.all().order_by('name'),
                            required=False,
                            widget=FilteredSelectMultiple("Brands", is_stacked=False))
    categories = forms.ModelMultipleChoiceField(
                            queryset=Category.objects.all().order_by('name'),
                            required=False,
                            widget=FilteredSelectMultiple("Categories", is_stacked=False))
    products = forms.ModelMultipleChoiceField(
                            queryset=Product.objects.all().order_by('name'),
                            required=False,
                            widget=FilteredSelectMultiple("Products", is_stacked=False))

    def __init__(self, *args, **kwargs):
        # ... we'll get back to this __init__ in a second ... 

    class Meta:
        model = Coupon
        exclude = ('filter_by',)  # Exclude because we're gonna build this field manually

然后告诉ModelAdmin类让我的优惠券使用该表单而不是默认表单:

^{pr2}$

执行此操作将在公式集的处显示三个表单的手动添加字段(brandcategoriesproducts)。换句话说:与我的Coupon模型中的其他字段相比,这生成了三个新字段,它们位于同一级别。但是:它们并不是真正的“第一类”字段,因为它们实际上要确定我的模型中某个特定字段的内容(即Coupon.filter_by字段),让我们记住,这是一个看起来或多或少像字典的字段:

^{3}$

为了让使用管理网页的人明白这三个字段并不是优惠券模型中“真正”的第一级字段,我决定将它们分组显示。在

为此,我需要更改字段的CouponsAdmin布局。我不希望这个分组影响我的Coupon模型的其他字段的显示方式,即使后来有新字段添加到模型中,所以我让表单的其他字段保持不变(换句话说:只对表单中的brandscategoriesproducts字段应用特殊/分组布局)。令我惊讶的是,我不能在ModelForm类中执行此操作。我不得不去ModelAdmin(我真的不知道为什么…):

class CouponsAdmin(admin.ModelAdmin):
    def get_fieldsets(self, request, obj=None):
        fs = super(CouponsAdmin, self).get_fieldsets(request, obj)
        # fs now contains only [(None, {'fields': fields})] meaning, ungrouped fields
        filter_by_special_fields = (brands', 'categories', 'products')
        retval = [
            # Let every other field in the model at the root level
            (None, {'fields': [f for f in fs[0][1]['fields']
                               if f not in filter_by_special_fields]
                    }),
            # Now, let's create the "custom" grouping:
            ('Filter By', {
                'fields': ('brands', 'categories', 'products')
            })
        ]
        return retval

    form = CouponAdminForm

有关fieldsetshere的详细信息

这就成功了:

Filter_by in the admin page

现在,当一个管理员用户通过这个表单创建了一个新的Coupon(换句话说:当用户单击页面上的“保存”按钮时),我会得到一个我在自定义表单中声明的额外字段的查询集(一个用于brands,另一个用于categories,另一个用于products),但我实际上需要转换这些信息在字典里。我可以通过重写模型形式的save方法来实现这一点:

class CouponAdminForm(forms.ModelForm):
    brands = forms.ModelMultipleChoiceField(queryset=Brand.objects.all().order_by('name'),
                                            required=False,
                                            widget=FilteredSelectMultiple("Brands", is_stacked=False))
    categories = forms.ModelMultipleChoiceField(queryset=Category.objects.all().order_by('name'),
                                                required=False,
                                                widget=FilteredSelectMultiple("Categories", is_stacked=False))
    products = forms.ModelMultipleChoiceField(queryset=Product.objects.all().order_by('name'),
                                              required=False,
                                              widget=FilteredSelectMultiple("Products", is_stacked=False))

    def __init__(self, *args, **kwargs):
        # ... Yeah, yeah!! Not yet, not yet... 

    def save(self, commit=True):
        filter_by_qsets = {}
        for key in ['brands', 'categories', 'products']:
            val = self.cleaned_data.pop(key, None)  # The key is always gonna be in 'cleaned_data',
                                                    # even if as an empty query set, so providing a default is
                                                    # kind of... useless but meh... just in case
            if val:
                filter_by_qsets[key] = val  # This 'val' is still a queryset

        # Manually populate the coupon's instance filter_by dictionary here
        self.instance.filter_by = {key: list(val.values_list('id', flat=True).order_by('id'))
                                   for key, val in filter_by_qsets.items()}
        return super(CouponAdminForm, self).save(commit=commit)


    class Meta:
        model = Coupon
        exclude = ('filter_by',)

“Save”上正确填充优惠券的filter_by字典。在

这里还剩下一些细节(使管理表单更加用户友好):当编辑一个现有的Coupon时,我希望表单的brandscategories和{}字段预先填充优惠券的filter_by字典中的值。在

这里是修改表单的__init__方法的地方(记住,我们正在修改的实例可以在表单的self.instance属性中访问)

class CouponAdminForm(forms.ModelForm):
    brands = forms.ModelMultipleChoiceField(queryset=Brand.objects.all().order_by('name'),
                                            required=False,
                                            widget=FilteredSelectMultiple("Brands", is_stacked=False))
    categories = forms.ModelMultipleChoiceField(queryset=Category.objects.all().order_by('name'),
                                                required=False,
                                                widget=FilteredSelectMultiple("Categories", is_stacked=False))
    products = forms.ModelMultipleChoiceField(queryset=Product.objects.all().order_by('name'),
                                              required=False,
                                              widget=FilteredSelectMultiple("Products", is_stacked=False))

    def __init__(self, *args, **kwargs):
        # For some reason, using the `get_changeform_initial_data` method in the
        # CouponAdminForm(forms.ModelForm) didn't work, and we have to do it
        # like this instead? Maybe becase the fields `brands`, `categories`...
        # are not part of the Coupon model? Meh... whatever... It happened to me the
        # same it happened to this OP in stackoverflow: https://stackoverflow.com/q/26785509/289011
        super(CouponAdminForm, self).__init__(*args, **kwargs)
        self.fields["brands"].initial = self.instance.filter_by.get('brands')
        self.fields["categories"].initial = self.instance.filter_by.get('categories')
        self.fields["products"].initial = self.instance.filter_by.get('products')

    def save(self, commit=True):
        filter_by_qsets = {}
        for key in ['brands', 'categories', 'products']:
        # ... explained above ...

就这样。在

到目前为止(右现在,2017年3月19日),这似乎很好地满足了我的需要。在

正如alfonso.kim在他的回答中指出的,我可以不能动态过滤不同的字段,除非我更改窗口的Javascrip(或者我可能使用^{}自定义模型?不知道:没有尝试过)我的意思是,用这种方法,我不能过滤管理网页上的选择框删除产品例如,如果用户选择一个brand、过滤器categories和{},那么它们只显示属于该品牌“的元素。这是因为当用户选择一个品牌时,浏览器和服务器之间没有XHR(Ajax)请求。基本上:流程是您得到表单>填写表单>发布表单,当用户点击表单上的“东西”时,浏览器和服务器之间没有通信。如果用户在brands选择中选择“可口可乐”,那么products选择将被过滤,并从可用产品中删除plastic bags,但很好。。。这种方法非常适合我的需要。在

请注意:这个答案中的代码可能包含一些多余的操作,或者本可以写得更好的东西,但是到目前为止,它似乎工作正常(谁知道呢,也许几天后我将不得不编辑我的答案,说“我完全错了!!请不要这样做!”但到目前为止,似乎还可以)不用说:我欢迎任何人对建议的任何评论,任何人都必须说:-)

我希望这对将来的人有帮助。在

您需要一些javascript将json字典放入一个漂亮的HTML小部件中,然后在Django处理程序中处理它。在

如果您想使用Django admin的“魔力”,您必须为其提供所需的输入,以呈现漂亮的UI并为您的折扣系统创建模型:

class Discount(models.Model):
  discount_type = models.TextField()
  discount_percentage = models.FloatField()

class DiscountElement(models.Model):
  discount = models.ForeignKey(Discount)
  manufacturer = models.ForeignKey(Manufacturer, null=True)
  category = models.ForeignKey(Category, null=True)

相关问题 更多 >