我有一个模型,其中一个字段是postgres.fields.JSONField。
将要存储在那里的Json是一个ID变量字典,引用数据库中的其他项(可能的关系/属性)。
请允许我更具体一点:
基本上,我正在尝试创建折扣系统,其中一些折扣将适用于某些产品。 JSON字段包含了解哪些产品可以获得折扣的约束。
例如:
如果我想对属于“饮料”类别的所有产品应用50%的折扣,并且“饮料”类别在数据库中具有标识5
,则折扣记录看起来像:
discount_type='percent'
discount='0.5'
filter_by={
'category': [5]
}
如果我想对“饮料”类别 AND 中的所有产品申请减免20美元,比如CocaCola制造,那么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%的折扣(让我们说id
为3
的产品),字典会如下:
discount_type='percent'
discount='0.25'
filter_by={
'id': [3]
}
这个想法似乎足够灵活满足我的需求,而且我很高兴(到目前为止)它。
现在,问题在于如何在Discount
模型的Django管理区域中输入这些值。
正如预期的那样,filter_by
字典呈现的文本字段最初如下所示:
如果我想为其添加字段,我需要写出我想要的确切JSON ...这意味着如果我想对“Beverages”类别应用折扣,我需要弄清楚该类别在数据库中有哪个ID,然后手动输入{"category": [5]}
,同时在输入'
:
时要非常小心,确保我不要不要错过]
或[
...
Thaaaat ......好吧,这不是很有帮助......
由于我只是要过滤几个字段(category
,manufacturer
,product
...),这些字段实际上是数据库其他元素的ID列表,我想显示一个可以过滤的 thingy 的大型MultiSelect框,所以我可以看到我可以过滤的所有元素的用户友好列表,选择一些,然后,当我点击在“创建折扣”上,我会得到filter_by
字典(我还远没有担心如何生成字典,因为我甚至不知道如何正确呈现字典管理员表格。
像Django Admin自动为我的产品类别做的事情:
这真的非常好:一种产品可以属于多个类别。为此,Django并排呈现两个<select multiple
框,包含可用的类别,以及产品已经属于的类别......我可以通过鼠标的笔划添加/删除类别...真的,非常好。但Django可以做到这一点,因为它知道categories
模型中的ManyToMany
是Product
关系。
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
,category
或manufacturer
没有product
字段。可怜的Django不知道Discount
与所有这些事情有关:它只知道有一个Json
字段。
我真的希望能够在Django区域展示一堆<select>
列出所有可能的过滤器(Category
,Manufacturer
,ID
.. 。)可以存储在filter_by
字典中(一个条目带有<select>
的{{1}} Category
,显示数据库中的所有可用类别,Manufacturer
的一个条目,显示所有可用的制造商......等等。但我真的,真的不知道该怎么做。
我可以使用Widgets
尝试通过form
通过forms.ModelMultipleChoiceField
来表示JSON字段(顺便提一下,似乎是最接近我想要的东西,虽然仍然很远)...但我觉得这有点无意义,因为没有什么能接近我想要的东西。
像往常一样,感谢您阅读这封巨大的电子邮件,并提前感谢您。任何提示都会非常感激,即使只是你应该看看“这个”
答案 0 :(得分:1)
你需要一些javascript来使json字典适合一个漂亮的HTML小部件,然后在Django处理程序中处理它。
如果你想使用&#34;魔法&#34; Django管理员,您必须为其提供所需的输入,以便为您的折扣系统呈现漂亮的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)
答案 1 :(得分:1)
所以...我很欣赏@ alfonso.kim的answer,但是想要为&#34;渲染&#34;而创建一个全新的Django模型的想法。 目的对我来说听起来有点过分。请!不要误会我的意思:它可能是&#34;规范&#34;这样做的方式(我已经多次推荐这种方法)并且可能比我更好,但我想表明我如何解决我的问题特别的问题:
我看了一下Django的源代码,特别是如何在管理员中显示ManyToMany
关系。如果你看看我上面的原始问题,我想弄清楚Django在编辑一个产品时使用哪个类来显示类别(&#34;双列选择&#34; ,给它一个名字,我非常喜欢)。事实证明这是一个django.forms.models.ModelMultipleChoiceField,&#34;经验丰富的&#34; ,带有一个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
课程我的优惠券使用该表格而不是默认表格:
class CouponsAdmin(admin.ModelAdmin):
form = CouponAdminForm
# ... #
admin.site.register(Coupon, CouponsAdmin)
这样做会在处方集的 root 中显示三个表格的手动添加字段(brand
,categories
和products
)。换句话说:这产生了三个与我Coupon
模型中其他字段相同级别的新字段。然而:他们不是真正的第一堂课&#34;字段,因为它们实际上将确定我的模型中的一个特定字段的内容(Coupon.filter_by
字段),让我们记住,这是一个看起来或多或少的字典:
filter_by = {
"brands": [2, 3],
"categories": [7]
}
为了使用管理员网页向人们明确表示这三个字段并非真实地&#34;优惠券模型中的第一级字段,我决定将它们分组显示。
为此,我需要更改字段的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
here
这就是诀窍:
现在,当管理员用户通过此表单创建新的Coupon
时(换句话说:当用户点击页面上的&#34; Save&#34;按钮时),我会得到一个查询集我在自定义表单中声明的额外字段(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',)
在&#34; Save&#34; 上正确填充了优惠券的filter_by
词典。
还有一些细节(为了使管理员形式更加用户友好):编辑现有 Coupon
时,我想要brands
,{表单的{1}}和categories
字段预先填充了优惠券的products
字典中的值。
此处修改表单的filter_by
方法非常方便(请记住,我们正在修改的实例可在__init__
中访问表格的属性)
self.instance
就是这样。
截至目前(正确现在,2017年3月19日),这似乎可以很好地满足我的需求。
正如alfonso.kim在他的回答中指出的那样,除非我更改窗口的Javascrip(或者我可能使用{{3} }自定义模型?不知道:没有尝试过)我的意思是,通过这种方法,我无法过滤管理网页上的选择框,删除仅属于所选类别的产品,例如,我不能做&#34;如果用户选择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 ...
,过滤brand
和categories
,那么他们只会显示属于该品牌的元素&#34 ; 。发生这种情况是因为当用户选择品牌时,浏览器和服务器之间没有XHR(Ajax)请求。基本上:流程你获取表格 - &gt; 您填写表格 - &gt; 您发布表单,浏览器之间没有通信&lt; - &gt;当用户点击&#34;事物&#34;在表格上。如果用户选择&#34; Coca cola&#34;那将是很好的。在products
选择中,brands
选择被过滤,并从可用产品中删除products
(例如)但是......这种方法&#34; good足够的&#34; 满足我的需求。
请注意:这个答案中的代码可能包含一些冗余的操作,或者可能写得更好的东西,但到目前为止,似乎工作正常(谁知道,也许我必须几天后编辑我的回答说&#34;我完全错了!!请不要这样做!&#34; 但到目前为止它似乎没问题)毋庸置疑:我会欢迎任何人提出任何意见: - )
的意见我希望将来可以帮助某人。