limit choices in the drop down of subcategory based on related category in Django admin

后端 未结 1 1032
陌清茗
陌清茗 2021-01-23 20:29

I have three models as

class Category(models.Model):
       name = models.CharField(max_length=128)

class SubCategory(models.Model):
    category = models.Forei         


        
相关标签:
1条回答
  • It seems that is the best way to realize this relations, one model with parent relation. Here is my solution which is based on native django fields,templatetags and little custom of admin template. In example, i generate custom select element with tabbed childs (based on native django). Example of select field (sorry, example in Russian language):

    Realization of Select class(for edit and create):

    class mSelect(Widget):
        def __init__(self, attrs=None, choices=()):
            super(mSelect, self).__init__(attrs)
            # choices can be any iterable, but we may need to render this widget
            # multiple times. Thus, collapse it into a list so it can be consumed
            # more than once.
            self.choices = list(choices)
    
        def render(self, name, value, attrs=None, choices=()):
    
            if value is None: value = ''
            final_attrs = self.build_attrs(attrs, name=name)
            print name
            output = [u'<select name=\"%s\" style=\"width:200px;\">' % name]
            output.append(u"<option value=\"\"%s>----------</option>")
            options = self.render_options(choices, [value])
            if options:
                output.append(options)
            output.append('</select>')
            return mark_safe(u'\n'.join(output))
    
        def render_options(self, choices, selected_choices):
    
            def render_option(option_value, option_label):
                option_value = force_unicode(option_value)
                selected_html = (option_value in selected_choices) and u' selected="selected"' or u''
                return u'<option value="%s"%s style=\"padding-left:20px;\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;%s</option>' % (
                    escape(option_value), selected_html,
                    conditional_escape(force_unicode(option_label)))
            # Normalize to strings.
            selected_choices = set([force_unicode(v) for v in selected_choices])
            output = []
    
            for option_value, option_label in chain(self.choices, choices):
                childs = **YOUR_MODEL**.objects.filter(parent=option_value).order_by("**YOUR_FIELDNAME**")
    
                if len(childs)>0:
                    output.append("<option value=\"%s\" disabled >%s</option>" % (option_value,option_label))
    
                    for child in childs:
                        output.append(render_option(child.id, child.iname))
    
            return u'\n'.join(output)
    

    Then you need to create modelAdmin class for you model:

    Example:

    class **YOUMODELADMIN**(admin.ModelAdmin):
        .....
        .....
        .....
    
        def formfield_for_dbfield(self, db_field, **kwargs):
            if db_field.name == '**YOUR_FIELD_NAME**':
                kwargs["widget"] = mSelect()
            field = super(**YOUMODELADMIN**, self).formfield_for_dbfield(db_field, **kwargs)  
            return field
    

    If you need to show this relations in admin(list_filter) i think the best way is to write templatetag for this field+javascript function to show relation tree. Example(image+code): (copy file change_list.html into your template folder like: templates/admin/App/model or yourappname/templates/admin/yourmodelname/ change_list.html Then add call of your template tag in list filter block:

    Example of Javascript block:

    <script>
        function showTree(name_id)
        {
            if(document.getElementById("li_" + name_id).style.display=="none")
            {
                //document.getElementById("div_" + name_id).style.display = "block";
                document.getElementById("li_" + name_id).style.display = "block";
            }
            else
            {
                //document.getElementById("div_" + name_id).style.display = "none";
                document.getElementById("li_" + name_id).style.display = "none";
            }
        }
    </script>
    

    Example code of templatetag(python):

    def YOURTEMPLATETAG(request):
        root_link = "/admin/YOURAPP/YOURMODEL/"
        mvar = "YOURFIELD_ID__id__in"
        mlink = root_link + "?"
    
        for item in request.GET:
            if item!=mvar:
                mlink += "&%s=%s" % (item,request.GET[item])    
    
        arr_options = []
        dest = HERE YOU GET YOUR ROOT OBJECTS
        selected = ""
    
        for item in dest:
            show = False
            childs = HERE YOU GET YOU CHILD OBJECTS
    
            if len(childs)>0:
                str_req = "".join("%d," % child.id for child in childs).rstrip(",")
    
                if u"ptype__id__in" in request.GET:
                    selected = request.GET["YOURFIELDNAME__id__in"]
                    if selected in str_req.split(","):
                        show = True
    
                proot = {"name":item.iname,"link":str_req,"childs":childs,"show":show}
    
                arr_options.append(proot)
    
        if "," not in selected and len(selected)>0:
            selected = int(selected)
        return render_to_string("PATH_TO_YOUR_TEMPLATETAG_TEMPLATE/templatetags/show_options.html",{"mlink":mlink,"selected":selected,"options":arr_options,"name":u"YOUR FILTER NAME","tst":request})
    

    Example of template for templatetag:

    <h3>{{name}}</h3>
    <ul>
        <!--li class="selected"?q=-->
        <li{% ifequal selected '' %} class="selected"{% endifequal %}><a href="?">Все</a></li>
        {% for item in options %}
            <li  {% ifequal selected item.link %} class="selected"{% endifequal %} >
                <a href="{{mlink}}&YOURFIELDNAME__id__in={{item.link}}">{{item.name}}</a>
                <a href="javascript:showTree('{{item.link}}')">[show tree]</a>
            </li>
    
            <li id="li_{{item.link}}" {% ifequal item.show 1 %}{%else%}style="display:none;"{%endifequal%}>
                <ul>
            {% for child in item.childs %}
                <li {% ifequal selected child.id %} class="selected"{% endifequal %}><div style="margin-left:10px;"><a href="{{mlink}}&YOURFIELDNAME__id__in={{child.id}}">{{child.FIELDOFNAME}}</a></div></li>
            {% endfor %}
                </ul>
            </li>
    
        {% endfor %}
    </ul>
    

    And finally block for change_list.html:

    ....
    ....
              <div id="changelist-filter" style="width:350px;z-index:0;">
                {% load YOURTEMPLATETAGFILE %}
                {% show_date_cal request "/PATH_TO_YOUR_MODEL_VIEW/" %}
                <h2>Custom filters</h2>
                {% TEMPLATETAGNAME request %}
    
    
                <h2>{% trans 'Filter' %}</h2>
                {% for spec in cl.filter_specs %}{% admin_list_filter cl spec %}{% endfor %}
    ...
    .....
    

    I think anyway this example will be useful for creating custom controls+admin filters Sorry if not )))

    0 讨论(0)
提交回复
热议问题