我有一个模型,其中一个字段是postgres.fields.JSONField。
要存储的Json有一个in变量字典,引用数据库中的其他项(可能的关系/属性)。
请允许我更具体地:
基本上,我试图创建一个折扣系统,其中一些折扣将适用于某些产品。JSON字段包含知道哪些产品可以获得折扣的约束。
例如:
5,则折扣记录如下:
折扣类型=‘百分比’折扣=‘0.5’filter_by={‘类别’:5}filter_by字典看起来应该是这样的:
折扣_type=‘固定数量’折扣=‘20’filter_by={‘类别’:5,‘制造商’:2#假设可口可乐是数据库“制造商”#表中含有id==2的制造商#(注意:这是必需的,因为CocaCola生产除了“饮料”之外的#产品)id为3的产品),则字典如下所示:
折扣类型=‘百分比’折扣=‘0.25’filter_by={ 'id':3}这个想法似乎足够灵活,满足我的需要,我很高兴(到目前为止)它。
现在,问题是如何在模型的Django管理区域中输入--这些值。
正如预期的那样,filter_by字典将呈现为一个文本字段,最初如下所示:

如果我想将字段添加到其中,我需要编写我想要的JSON .这意味着,如果我想对“饮料”类别应用折扣,我需要找出该类别在数据库中的ID,然后手动键入{"category": [5]},同时在键入'、:时非常小心,确保我不会错过]或[.
这是..。嗯,这不是很有帮助.
因为我只需要几个字段(category,manufacturer,product.)它们实际上是数据库中其他元素的ID列表,我想为每个可以过滤的元素显示一个大的MultiSelect框,这样我就可以看到一个用户友好的列表,列出我可以过滤的所有元素,选择几个,然后,当我单击"Create“时,我会得到filter_by字典(我仍然不担心如何生成字典,因为我甚至不知道如何正确地呈现Admin表单)。
就像Django Admin自动为我的产品类别所做的那样:

这真是太好了:一个产品可以属于几个类别。为此,Django并行呈现两个<select multiple框,其中包含可用的类别,以及产品已经属于的类别.我可以通过鼠标的笔划添加/删除类别.真的真的很好。但是Django可以这样做,因为它知道categories是Product模型中的一个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模型的问题是category、manufacturer或product没有ManyToMany字段。可怜的Django不知道Discount与所有这些事情都相关:它只知道有一个Json字段。
我真的很想在Django地区展示一些<select>,列出所有可能的过滤器(Category,Manufacturer,ID.)它可以存储在filter_by字典中(一个带有双<select>的Category条目显示数据库中的所有可用类别,一个条目用于Manufacturer,显示所有可用的制造商.等等)。但我真的真的不知道怎么做。
我可以使用Widgets,尝试通过form,通过forms.ModelMultipleChoiceField来表示JSON字段(顺便说一句,这似乎是我想要的最接近的东西,尽管距离很远)。但我认为这是没有意义的,因为没有任何东西接近我想要的。
和往常一样,感谢您阅读这封巨大的电子邮件,并提前感谢您。任何暗示都会很感激,哪怕只是一个你应该看看“这个”。
发布于 2017-03-19 17:27:52
所以..。我很欣赏@alfonso.kim的回答,但是创建一个全新的Django模型只是为了“呈现”目的的想法对我来说似乎有点过分了。请!不要误解我的意思:这可能是一种“规范”的方法(我见过很多次推荐的方法),也许比I做的更好,但我想说明I是如何解决我的特定问题的:
我查看了Django的源代码,特别是在Admin中如何显示ManyToMany关系。如果您看我上面最初的问题,我想知道Django在编辑一个产品时使用哪个类来显示类别(“双列选择”,以便给它起一个我非常喜欢的名称)。原来它是一个django.forms.models.ModelMultipleChoiceField,带有一个FilteredSelectMultiple小部件的“经验丰富”。
根据这些信息,我为我的类创建了一个自定义管理表单,手动添加了我想要显示的字段:
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类让我的优惠券使用该表单而不是默认的表单:
class CouponsAdmin(admin.ModelAdmin):
form = CouponAdminForm
# ... #
admin.site.register(Coupon, CouponsAdmin)这样做将三个表单的手动添加字段(brand、categories和products)显示在公式的根部。换句话说:这产生了三个与我的Coupon模型中的其他字段相同级别的新字段。但是:它们不是普通的“第一类”字段,因为它们实际上将确定“我的模型”( Coupon.filter_by字段)中某个特定字段的内容,让我们记住,这是一本大致类似于以下内容的字典:
filter_by = {
"brands": [2, 3],
"categories": [7]
}为了让使用Admin网页的人明白这三个字段在优惠券模型中并不是真正的第一级字段,我决定将它们分组。
为此,我需要更改字段的CouponsAdmin布局。我不希望这个分组会影响我的Coupon模型的其他字段的显示方式,即使后来新字段被添加到模型中,所以我让表单中的其他字段保持不变(换句话说:只对表单中的brands、categories和products字段应用特殊/分组布局)。令我惊讶的是,我无法在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有关fieldsets 这里的更多信息
这就成功了:

现在,当管理用户通过这个表单创建一个新的Coupon (换句话说:当用户单击页面上的"Save“按钮)时,我将获得一个查询集,用于我在自定义表单中声明的额外字段(一个用于brands的查询集,另一个用于categories的查询集,另一个用于products的查询集),但实际上我需要将该信息转换为字典。我能够通过覆盖模型的表单的方法来实现这一点。
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',)在“保存”上正确地填充了优惠券的filter_by字典。
还有一些细节(为了使管理表单更方便用户):在编辑现有的Coupon时,我希望表单的brands、categories和products字段预先填充优惠券的filter_by字典中的值。
修改表单的方法非常有用(请记住,我们正在修改的实例在表单的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 ...就是这样。
到目前为止(现在的 now ,2017年3月19日),这似乎很好地满足了我的需求。
正如alfonso.kim在他的回答中所指出的,除非我更改了窗口的Javascrip (或者我可能使用了ChainedForeignKey自定义模型),否则我不能动态地过滤不同的字段。不知道:没有尝试)我的意思是,使用这种方法,我不能过滤管理网页上的复选框,删除只属于所选类别的产品,例如,我不能这样做,比如“如果用户选择一个brand__,筛选categories和products,使它们只显示属于该品牌的元素”。这是因为当用户选择一个品牌时,浏览器和服务器之间没有XHR (Ajax)请求。基本上:流程是您得到表单->您填充了表单->您发布了表单,浏览器<-->服务器之间没有通信,而用户在表单上单击"things“。如果用户在brands选择中选择“可口可乐”,则products选择会被过滤,并从可用产品(例如)中删除plastic bags,这将是一件好事。这种方法对我的需要“足够好”。
请注意:这个答案中的代码可能包含一些多余的操作,或者一些可以写得更好的东西,但到目前为止,它似乎还能正常工作(谁知道,也许几天后我不得不编辑我的答案,说:“我完全错了!!请不要这样做!”但到目前为止,()似乎还可以)不用说:我欢迎任何关于任何人必须说:-)的建议的评论
我希望这对将来的人有帮助。
发布于 2017-03-12 03:55:23
您将需要一些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)https://stackoverflow.com/questions/42742903
复制相似问题