最新消息:20210917 已从crifan.com换到crifan.org

【已解决】给内容管理系统中添加按Topic去筛选

Django crifan 1668浏览 0评论

原本的系统中,不支持按topic筛选:

希望希望加上:

按照topic筛选

而发现新建剧本页面中有类似的功能:

所以参考:

src/routes/Script/ScriptCreate.js

最后整理过来代码:

<code>
export default class ScriptList extends PureComponent {
  state = {
    formValues: {},
    currentPage: 1,
    pageSize: 20,
    first_level_topic: [],
    second_level_topic: [],
  };

  componentDidMount() {
    const { dispatch } = this.props;

    const params = {
      page_size: 1000,
    };
    dispatch({
      type: 'topic/fetch',
      payload: params,
    });
  }

    const topicList = this.props.topic.topics;

    const firstLevelOptions = Object.keys(topicList).map(first =&gt; &lt;Option key={first}&gt;{first}&lt;/Option&gt;);
    const secondLevelOptions = this.state.first_level_topic.map(second =&gt; &lt;Option key={second}&gt;{second}&lt;/Option&gt;);

    const selectFormItemLayout = {
      labelCol: {
        xs: { span: 24 },
        sm: { span: 7 },
      },
      wrapperCol: {
        xs: { span: 36 },
        sm: { span: 18 },
        md: { span: 16 },
      },
    };


          &lt;Col md={12} sm={18}&gt;
            &lt;FormItem {...selectFormItemLayout} label="Topic" style={{ marginBottom: 5 }} &gt;
              {getFieldDecorator('topic', {
                rules: [{ required: false, message: '请选择Topic'}],
              })(
                &lt;Select
                  showSearch
                  optionFilterProp="children"
                  filterOption={(input, option) =&gt; option.props.children.indexOf(input) &gt;= 0}
                  style={{ width: '46%' }}
                  onChange={this.handleFirstOptionChange.bind(this, topicList)}
                  placeholder="一级Topic"
                &gt;
                  {firstLevelOptions}
                &lt;/Select&gt;
              )}
              &lt;span style={{ marginLeft: 5 }} className="ant-form-text"&gt; - &lt;/span&gt;
              {getFieldDecorator('second_level_topic', {
                rules: [{ required: false, message: '请选择Topic'}],
              })(
                &lt;Select
                  showSearch
                  optionFilterProp="children"
                  filterOption={(input, option) =&gt; option.props.children.indexOf(input) &gt;= 0}
                  style={{ width: '46%' }}
                  onChange={this.onSecondOptionChange.bind(this, topicList)}
                  placeholder="二级Topic"
                &gt;
                  {secondLevelOptions}
                &lt;/Select&gt;
              )}
            &lt;/FormItem&gt;
          &lt;/Col&gt;

</code>

结果运行出错:

<code>TypeError: Cannot read property 'topics' of undefined
ScriptList.renderSimpleForm
src/routes/Script/ScriptList.js:196
  193 | renderSimpleForm() {
  194 |   const { getFieldDecorator } = this.props.form;
  195 |
&gt; 196 |   const topicList = this.props.topic.topics;
</code>

自己查找对比了后,最终找到是,在connect中,加上映射,即可获得对应参数:

<code>@connect(({ script, topic, loading }) =&gt; ({
  script,
  topic,
  loading: loading.models.script,
}))
@Form.create()
export default class ScriptList extends PureComponent {
</code>

界面上至少出来了一级和二级的topic的筛选下拉框了:

然后再去研究,如何后端加上支持这个筛选的条件的查询:

先去看前端调用的api接口:

是:

<code>http://localhost:xxx/api/v1/scripts/?topic=daily%20routine&amp;second_level_topic=celebrations
</code>

然后再去找server端代码

找到了:

apps/script/views.py

加上log日志看看:

<code>class ScriptViewSet(mixins.ListModelMixin,

    def list(self, request, *args, **kwargs):
        """
            获取 script list,取 author=request.user。按历史记录 version 最新的一个script,
            组成列表
        """
        search = request.query_params.get('search', '')
        publish_status = request.query_params.get('publish_status', '')
        #/api/v1/scripts/?topic=Animal&amp;second_level_topic=farm%20animal&amp;publish_status=1
        topic = request.query_params.get('topic', '')
        second_level_topic = request.query_params.get('second_level_topic', '')
        logger.info("search=%s,publish_status=%s,topic=%s,second_level_topic=%s",
            search, publish_status, topic, second_level_topic)
</code>

果然看到相关的调试期间的输出了:

<code>INFO|20180718 09:42:49|views:list:66|search=,publish_status=,topic=daily routine,second_level_topic=celebrations
</code>

然后再去看看如何加进去query的参数:

【已解决】Django项目中的Q中的xxx__icontains是什么意思

【已解决】Django中用Q的filter出错:django.core.exceptions.FieldError: Related Field got invalid lookup: icontains

然后发现逻辑上有问题,当一级topic和二级topic,是逻辑与的关系,不是或:

<code>filter_condition = filter_condition | Q(topic__name__icontains=topic)
</code>

所以要去找如何改为逻辑与

https://docs.djangoproject.com/zh-hans/2.0/topics/db/queries/

搜:|

找到:

“Q objects can be combined using the & and | operators. When an operator is used on two Q objects, it yields a new Q object.”

改为:

<code>filter_condition = filter_condition &amp; Q(topic__name__icontains=topic)
</code>

去试试结果是可以的。

然后再去优化:

此处希望二级topic可选,默认不选择

而现在问题是:

最初始化时,一级和二级topic都是默认没选择的:

但是当选择了一级topic,而二级topic默认就会选择(第一个):

希望即使切换选择了一级topic,二级也默认不选择(只是内部列表刷新)

然后用户根据自己需要,决定是否再去选择二级topic

然后改为:

<code>  handleFirstOptionChange(topicList, value) {
    const { form } = this.props;
    form.setFieldsValue({
      topic: value,
      // second_level_topic: topicList[value][0] || '',
      second_level_topic:  '', // when first topic change, clear second topic
    });

    this.setState({
      first_level_topic: topicList[value],
      // second_level_topic: topicList[value][0] || '',
      second_level_topic: '', // when first topic change, clear second topic
    });
  }
</code>

就可以了:

然后再去修复逻辑错误:

之前的查询query的条件,都是逻辑或的,实际上应该是逻辑与

否则就会出现:条件越多,反而查询出来的结果越多了:

然后改为:

<code>        filter_condition = Q()
        if search:
            filter_condition = filter_condition | Q(place__icontains=search) | Q(title__icontains=search)
        logger.info("after search: filter_condition=%s", filter_condition)

        if publish_status:
            if publish_status != '3':
                # filter_condition = filter_condition | Q(publish_status=publish_status)
                filter_condition = filter_condition &amp; Q(publish_status=publish_status)
        logger.info("after publish_status: filter_condition=%s", filter_condition)

        if topic:
            # filter_condition = filter_condition | Q(topic__icontains=topic)
            # filter_condition = filter_condition | Q(topic__name__icontains=topic)
            filter_condition = filter_condition &amp; Q(topic__name__icontains=topic)

        if second_level_topic:
            # filter_condition = filter_condition | Q(second_level_topic__icontains=second_level_topic)
            # filter_condition = filter_condition | Q(second_level_topic__name__icontains=second_level_topic)
            filter_condition = filter_condition &amp; Q(second_level_topic__name__icontains=second_level_topic)

        logger.info("finnal: filter_condition=%s", filter_condition)
</code>

才实现了正确的逻辑:

当title或place输入了值后,查询title或place包含该字符的内容,整个作为Q的查询条件和后续的逻辑与&

而publish_status,topic和second_level_topic分别都是逻辑与&

这样才能实现准确的查询

【总结】

此处,为了给剧本列表中添加支持topic搜索,完整的改动是:

web端:

src/common/router.js

<code>    '/script/script-detail': {
      // component: dynamicWrapper(app, ['script'], () =&gt; import('../routes/Script/ScriptDetail')),
      component: dynamicWrapper(app, ['script', 'topic'], () =&gt; import('../routes/Script/ScriptDetail')),
    },
</code>

src/routes/Script/ScriptList.js

<code>@connect(({ script, topic, loading }) =&gt; ({
  script,
  topic,
  loading: loading.models.script,
}))
@Form.create()
export default class ScriptList extends PureComponent {
  state = {
    formValues: {},
    currentPage: 1,
    pageSize: 20,
    first_level_topic: [],
    second_level_topic: [],
  };
  componentDidMount() {
    const { dispatch } = this.props;
    dispatch({
      type: 'script/fetch',
    });

    // topic select 取所有的 topic
    const params = {
      page_size: 1000,
    };
    dispatch({
      type: 'topic/fetch',
      payload: params,
    });
  }

  handleFirstOptionChange(topicList, value) {
    const { form } = this.props;
    form.setFieldsValue({
      topic: value,
      // second_level_topic: topicList[value][0] || '',
      second_level_topic:  '', // when first topic change, clear second topic
    });

    this.setState({
      first_level_topic: topicList[value],
      // second_level_topic: topicList[value][0] || '',
      second_level_topic: '', // when first topic change, clear second topic
    });
  }

  onSecondOptionChange(topicList, value) {
    const { form } = this.props;
    form.setFieldsValue({
      second_level_topic: value || '',
    });

    this.setState({
      second_level_topic: value || '',
    });
  }

  renderSimpleForm() {
    const { getFieldDecorator } = this.props.form;

    const topicList = this.props.topic.topics;

    const firstLevelOptions = Object.keys(topicList).map(first =&gt; &lt;Option key={first}&gt;{first}&lt;/Option&gt;);
    const secondLevelOptions = this.state.first_level_topic.map(second =&gt; &lt;Option key={second}&gt;{second}&lt;/Option&gt;);

    const selectFormItemLayout = {
      labelCol: {
        xs: { span: 24 },
        sm: { span: 7 },
      },
      wrapperCol: {
        xs: { span: 36 },
        sm: { span: 18 },
        md: { span: 16 },
      },
    };

    return (
      &lt;Form onSubmit={this.handleSearch} layout="inline"&gt;
        &lt;Row gutter={{ md: 8, lg: 24, xl: 48 }}&gt;
          &lt;Col md={6} sm={18}&gt;
            &lt;FormItem label="Place/Title"&gt;
              {getFieldDecorator('search')(&lt;Input placeholder="Place/Title" /&gt;)}
            &lt;/FormItem&gt;
          &lt;/Col&gt;

          &lt;Col md={12} sm={18}&gt;
            &lt;FormItem {...selectFormItemLayout} label="Topic" style={{ marginBottom: 5 }} &gt;
              {getFieldDecorator('topic', {
                rules: [{ required: false, message: '请选择Topic'}],
              })(
                &lt;Select
                  showSearch
                  optionFilterProp="children"
                  filterOption={(input, option) =&gt; option.props.children.indexOf(input) &gt;= 0}
                  style={{ width: '46%' }}
                  onChange={this.handleFirstOptionChange.bind(this, topicList)}
                  placeholder="一级Topic"
                &gt;
                  {firstLevelOptions}
                &lt;/Select&gt;
              )}
              &lt;span style={{ marginLeft: 5 }} className="ant-form-text"&gt; - &lt;/span&gt;
              {getFieldDecorator('second_level_topic', {
                rules: [{ required: false, message: '请选择Topic'}],
              })(
                &lt;Select
                  showSearch
                  optionFilterProp="children"
                  filterOption={(input, option) =&gt; option.props.children.indexOf(input) &gt;= 0}
                  style={{ width: '46%' }}
                  onChange={this.onSecondOptionChange.bind(this, topicList)}
                  placeholder="二级Topic"
                &gt;
                  {secondLevelOptions}
                &lt;/Select&gt;
              )}
            &lt;/FormItem&gt;
          &lt;/Col&gt;
</code>

server端:

/apps/script/views.py

<code>class ScriptViewSet(mixins.ListModelMixin,
                    mixins.CreateModelMixin,
                    mixins.RetrieveModelMixin,
                    PutOnlyUpdateModelMixin,
                    mixins.DestroyModelMixin,
                    viewsets.GenericViewSet):
    queryset = Script.objects.all()

    def list(self, request, *args, **kwargs):
        #/api/v1/scripts/?topic=Animal&amp;second_level_topic=farm%20animal&amp;publish_status=1
        topic = request.query_params.get('topic', '')
        second_level_topic = request.query_params.get('second_level_topic', '')
        logger.info("search=%s,publish_status=%s,topic=%s,second_level_topic=%s",
            search, publish_status, topic, second_level_topic)
        filter_condition = Q()
        if search:
            filter_condition = filter_condition | Q(place__icontains=search) | Q(title__icontains=search)
        logger.info("after search: filter_condition=%s", filter_condition)

        if publish_status:
            if publish_status != '3':
                # filter_condition = filter_condition | Q(publish_status=publish_status)
                filter_condition = filter_condition &amp; Q(publish_status=publish_status)
        logger.info("after publish_status: filter_condition=%s", filter_condition)

        if topic:
            # filter_condition = filter_condition | Q(topic__icontains=topic)
            # filter_condition = filter_condition | Q(topic__name__icontains=topic)
            filter_condition = filter_condition &amp; Q(topic__name__icontains=topic)

        if second_level_topic:
            # filter_condition = filter_condition | Q(second_level_topic__icontains=second_level_topic)
            # filter_condition = filter_condition | Q(second_level_topic__name__icontains=second_level_topic)
            filter_condition = filter_condition &amp; Q(second_level_topic__name__icontains=second_level_topic)

        logger.info("finnal: filter_condition=%s", filter_condition)
</code>

效果是:

转载请注明:在路上 » 【已解决】给内容管理系统中添加按Topic去筛选

发表我的评论
取消评论

表情

Hi,您需要填写昵称和邮箱!

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址
92 queries in 0.187 seconds, using 23.42MB memory