Bor*_*jaX 4 python django customization django-admin django-jsonfield
我有一个模型,其中一个字段是postgres.fields.JSONField。
将要存储在那里的 Json 是引用数据库中其他项目(可能的关系/属性)的 ID 变量字典。
请允许我更具体:
基本上,我正在尝试创建一个折扣系统,其中某些折扣适用于某些产品。JSON 字段包含了解哪些产品可以获得折扣的约束。
例如:
如果我想对属于“饮料”类别的所有产品应用 50% 的折扣,并且“饮料”类别5在数据库中具有 id ,则折扣记录将如下所示:
discount_type='percent'
discount='0.5'
filter_by={
'category': [5]
}
Run Code Online (Sandbox Code Playgroud)如果我想$ 20 off适用于所有产品的“饮料”类别和由制造,比方说,可口可乐,该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")
}
Run Code Online (Sandbox Code Playgroud)如果我想对特定产品(比方说产品id是3)应用 25% 的折扣,字典将如下所示:
discount_type='percent'
discount='0.25'
filter_by={
'id': [3]
}
Run Code Online (Sandbox Code Playgroud)这个想法似乎对我的需要足够灵活,我很高兴(到目前为止)。
现在,问题是如何在模型的 Django 管理区域中输入这些值Discount。
正如预期的那样,filter_by字典呈现为文本字段,最初如下所示:
If I want to add fields to it, I need to write the exact JSON of what I want... Which means that if I want to apply a discount to the "Beverages" category, I need to go figure out which ID that category has in the database, and then manually type {"category": [5]}, while being extremely careful when typing the ', the :, make sure that I don't miss a ] or a [...
Thaaaat... well, that is not very helpful...
Since I am only going to be filtering by a few fields (category, manufacturer, product...) which are actually lists of IDs of other elements of the database, I would like to show a big MultiSelect box per thingy I can filter for, so I can see a user friendly list of all the elements I can filter by, select a few, and then, when I click on "Create discount", I would get the filter_by dictionary (I'm still far from worrying about how to generate the dictionary, since I don't even know how to properly render the Admin form).
Something like what Django Admin automatically did for my Products' categories:
That is really, really, nice: One product can belong to several categories. For that, Django renders, side by side, two <select multiple boxes, with the available categories, and the categories that the product already belongs to... I can add/remove categories through the stroke of a mouse... Really, really nice. But Django can do that because it knows that the categories are a ManyToMany relation in the Product model.
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)
Run Code Online (Sandbox Code Playgroud)
The problem with the Discount model is that there is no ManyToMany field to category, manufacturer or product. Poor Django doesn't know that a Discount is related to all those things: It only knows there's a Json field.
I would really like to be able to show a bunch of those <select> in the Django Area listing all the possible filters (Category, Manufacturer, ID...) that can be stored in the filter_by dictionary (one entry with the double <select> for Category showing all the available categories in the database, one entry for Manufacturer, showing all the available manufacturers... etcetera). But I really, really don't know how to do that.
I could bore you with a bunch of tries I've done, using Widgets, trying to represent the JSON field through a form, through forms.ModelMultipleChoiceField (which by the way, seems to have been the closest thing to what I want, although still very far)... But I think that is kind of pointless, since nothing came close to what I wanted.
As usual, thank you for reading this huge email and thank you in advance. Any hint will be really appreciated, even just a you should take a look to "this"
所以......我很欣赏@alfonso.kim 的回答,但是为了“渲染”目的而创建一个全新的 Django 模型的想法对我来说听起来有点矫枉过正。请!不要误会我的意思:这可能是做这件事(我见过的方法推荐很多次)的“规范”的方式,也许是比什么更好的我没有,但我想表现怎么我解决我的问题,特别是:
我查看了 Django 的源代码,特别是如何ManyToMany在 Admin 中显示关系。如果您查看我上面的原始问题,我想弄清楚 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
Run Code Online (Sandbox Code Playgroud)
然后告诉ModelAdmin我的优惠券班级使用该表格而不是默认表格:
class CouponsAdmin(admin.ModelAdmin):
form = CouponAdminForm
# ... #
admin.site.register(Coupon, CouponsAdmin)
Run Code Online (Sandbox Code Playgroud)
这样做会在公式的根部显示三个表单的手动添加字段(brand、categories和products)。换句话说:这在与我的模型中的其他字段相同的级别上产生了三个新字段。但是:它们并不是真正的“一流”字段,因为它们实际上是要确定我的模型(字段)中一个特定字段的内容,让我们记住,它是一个或多或少看起来像的字典:CouponCoupon.filter_by
filter_by = {
"brands": [2, 3],
"categories": [7]
}
Run Code Online (Sandbox Code Playgroud)
为了让使用 Admin 网页的人清楚这三个字段在 Coupon 模型中并不是“真正的”第一级字段,我决定将它们分组显示。
为此,我需要更改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
Run Code Online (Sandbox Code Playgroud)
关于这里的更多信息fieldsets
这就是诀窍:
现在,当管理员用户Coupon通过此表单创建一个新表单时(换句话说:当用户单击页面上的“保存”按钮时),我将获得一个查询集,用于我在自定义表单中声明的额外字段(一个查询集用于brands,另一个用于categories和另一个用于products) 但我实际上需要将该信息转换为字典。我能够通过覆盖saveModel's Form的方法来实现这一点:
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',)
Run Code Online (Sandbox Code Playgroud)
这filter_by在"Save"上正确填充了优惠券的字典。
还剩下一些细节(使管理表单更加用户友好):在编辑现有 时 Coupon,我希望表单的brands,categories和products字段预先填充filter_by优惠券字典中的值。
这就是修改Form的__init__方法派上用场的地方(记住我们正在修改的实例可以在Form的属性中访问)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: /sf/ask/1874985661/
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 ...
Run Code Online (Sandbox Code Playgroud)
就是这样。
到目前为止(现在,2017 年 3 月 19 日)这似乎很好地满足了我的需要。
正如alfonso.kim在他的回答中指出的那样,除非我更改窗口的 Javascrip(或者我可能使用自定义模型?不知道:没有尝试过),否则我无法动态过滤不同的字段ChainedForeignKey我的意思是这种方法我无法过滤管理网页上的选择框,删除仅属于所选类别的产品,例如,我不能执行诸如“如果用户选择brand, 过滤器categories,products因此它们只显示属于那个牌子”。发生这种情况是因为当用户选择一个品牌时,浏览器和服务器之间没有 XHR (Ajax) 请求。基本上:流程是您获取表格-->您填写表格-->您发布表单,当用户单击表单上的“事物”时,浏览器 <--> 服务器之间没有通信。如果用户在选择中选择“可口可乐” brands,那么products选择会被过滤,并plastic bags从可用产品中删除(例如),但是很好......这种方法“足够好”满足我的需求。
请注意:此答案中的代码可能包含一些多余的操作,或者本可以写得更好的内容,但到目前为止,它似乎工作正常(谁知道,也许我必须编辑我的答案一些几天后说“我完全错了!请不要这样做!”但到目前为止似乎还可以)不用说:我欢迎任何人必须说的任何建议评论:-)
我希望这对未来的人有所帮助。
| 归档时间: |
|
| 查看次数: |
2523 次 |
| 最近记录: |