ограничить выбор в раскрывающемся списке подкатегории на основе связанной категории в Django Admin - PullRequest
1 голос
/ 04 июня 2010

У меня есть три модели как

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

class SubCategory(models.Model):
    category = models.ForeignKey(Category)
    name = models.CharField(max_length = 400)

class Document(models.Model):
    category = models.ForeignKey(Category, null=True,blank=True,help_text=_('Required'))
    subcategory = models.ForeignKey(SubCategory, null=True, blank=True, help_text =_('Required'))
    title = models.CharField(max_length=300)

Теперь в интерфейсе администратора у меня есть категория, подкатегория и поле заголовка. Если пользователь пытается выбрать какую-либо подкатегорию, то только это должен отображаться элемент подкатегории, относящийся к категории. Простой пример - страна, раскрывающийся список состояний. Я пытаюсь получить его от Modelform, как

class DocumentAdminModelForm(ModelForm):

   def __init__(self, *args, **kwargs):    
       super(DocumentAdminModelForm, self).__init__(*args, **kwargs)
       self.fields['sub_category'] = forms.ModelChoiceField(queryset = SubCategory.objects.filter(category__id = self.fields['category'] ))

но это не работает. Должен ли я использовать некоторые AJAX и JQuery для этого или Есть ли другой способ предложить?

Спасибо

1 Ответ

0 голосов
/ 05 июня 2010

Кажется, что это лучший способ реализовать эти отношения, одна модель с родительским отношением. Вот мое решение, которое основано на нативных полях django, шаблонах и небольшом кастомном шаблоне администратора. Например, я создаю пользовательский элемент select с дочерними элементами с вкладками (на основе нативного django). Пример поля выбора (извините, пример на русском языке):

альтернативный текст http://img576.imageshack.us/img576/5269/selects.png

Реализация класса Select (для редактирования и создания):

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)

Затем вам нужно создать класс modelAdmin для вашей модели:

Пример:

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

Если вам нужно показать эти отношения в admin (list_filter), я думаю, что лучший способ - написать шаблон тега для этого поля + функцию javascript, чтобы показать дерево отношений. Пример (изображение + код): (скопируйте файл change_list.html в папку шаблонов, например: templates / admin / App / model или yourappname / templates / admin / yourmodelname / change_list.html Затем добавьте вызов вашего тега шаблона в блок фильтра списка:

альтернативный текст http://img714.imageshack.us/img714/5141/treetd.png

Пример блока Javascript:

<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>

Пример кода шаблона тега (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})

Пример шаблона для тега шаблона:

<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>

И, наконец, блок для 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 %}
...
.....

Думаю, в любом случае этот пример будет полезен для создания пользовательских элементов управления + фильтров администратора. Извините если нет)))

...