django学习 | 实战 # 微信自定义菜单

目录

实现的效果图

# 图1:django的后台管理系统之自定义菜单

# 图2:django的后台管理系统之新增自定义菜单

# 图3:在公众号上的实现效果

一、先看看我们需要做什么

1、看微信的文档

2、用到的工具 wechatpy

3、顺一下思路

二、代码实现

wechat/models.py:

wechat/utils.py:

wechat/admin.py:


由于上面那个原因,在做微信开发的时候若是启用了服务器配置,原本在微信后台设置的自动回复和自定义菜单将会失效。为解决这个问题,这里将会讲django实现微信自定义菜单功能

实现的效果图

近来在用python写微信的自定义菜单,先看效果截图如下:

# 图1:django的后台管理系统之自定义菜单

(相关代码实现见下文)

# 图2:django的后台管理系统之新增自定义菜单

(相关代码实现见下文)

# 图3:在公众号上的实现效果

OK,大概效果是长上面那亚子。接下来讲讲代码实现部分。


一、先看看我们需要做什么

1、看微信的文档

我们需要做微信自定义菜单,效果图是上面那些截图的样子。

先查看微信的开发文档,把菜单转换为微信所需要的格式长这样子:

微信文档-自定义菜单

2、用到的工具 wechatpy

用的wechatpy:一个微信 (WeChat) 的第三方 Python SDK, 实现了微信公众号、企业微信和微信支付等 API。

安装按照文档的要求来吧,安装在这里不说。直接拉到自定义菜单那边看看:wechatpy的自定义菜单

要的其实主要是create()和删除delete()

wechatpy/client/api/menu.py 工具类长这个亚子:

(不用自己写的!是wechatpy提供的!就看看它长啥样就好了,看看能怎么用它)

# -*- coding: utf-8 -*-
from __future__ import absolute_import, unicode_literals

from wechatpy.exceptions import WeChatClientException
from wechatpy.client.api.base import BaseWeChatAPI


class WeChatMenu(BaseWeChatAPI):

    def get(self):
        """
        查询自定义菜单。
        详情请参考
        http://mp.weixin.qq.com/wiki/16/ff9b7b85220e1396ffa16794a9d95adc.html

        :return: 返回的 JSON 数据包

        使用示例::

            from wechatpy import WeChatClient

            client = WeChatClient('appid', 'secret')
            menu = client.menu.get()

        """
        try:
            return self._get('menu/get')
        except WeChatClientException as e:
            if e.errcode == 46003:
                # menu not exist
                return None
            else:
                raise e

    def create(self, menu_data):
        """
        创建自定义菜单 ::

            from wechatpy import WeChatClient

            client = WeChatClient("appid", "secret")
            client.menu.create({
                "button":[
                    {
                        "type":"click",
                        "name":"今日歌曲",
                        "key":"V1001_TODAY_MUSIC"
                    },
                    {
                        "type":"click",
                        "name":"歌手简介",
                        "key":"V1001_TODAY_SINGER"
                    },
                    {
                        "name":"菜单",
                        "sub_button":[
                            {
                                "type":"view",
                                "name":"搜索",
                                "url":"http://www.soso.com/"
                            },
                            {
                                "type":"view",
                                "name":"视频",
                                "url":"http://v.qq.com/"
                            },
                            {
                                "type":"click",
                                "name":"赞一下我们",
                                "key":"V1001_GOOD"
                            }
                        ]
                    }
                ]
            })

        详情请参考
        https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421141013

        :param menu_data: Python 字典

        :return: 返回的 JSON 数据包
        """
        return self._post(
            'menu/create',
            data=menu_data
        )

    def update(self, menu_data):
        """
        更新自定义菜单 ::

            from wechatpy import WeChatClient

            client = WeChatClient("appid", "secret")
            client.menu.update({
                "button":[
                    {
                        "type":"click",
                        "name":"今日歌曲",
                        "key":"V1001_TODAY_MUSIC"
                    },
                    {
                        "type":"click",
                        "name":"歌手简介",
                        "key":"V1001_TODAY_SINGER"
                    },
                    {
                        "name":"菜单",
                        "sub_button":[
                            {
                                "type":"view",
                                "name":"搜索",
                                "url":"http://www.soso.com/"
                            },
                            {
                                "type":"view",
                                "name":"视频",
                                "url":"http://v.qq.com/"
                            },
                            {
                                "type":"click",
                                "name":"赞一下我们",
                                "key":"V1001_GOOD"
                            }
                        ]
                    }
                ]
            })

        详情请参考
        https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421141013

        :param menu_data: Python 字典

        :return: 返回的 JSON 数据包
        """
        return self.create(menu_data)

    def delete(self):
        """
        删除自定义菜单。
        详情请参考
        http://mp.weixin.qq.com/wiki/16/8ed41ba931e4845844ad6d1eeb8060c8.html

        :return: 返回的 JSON 数据包

        使用示例::

            from wechatpy import WeChatClient

            client = WeChatClient('appid', 'secret')
            res = client.menu.delete()

        """
        return self._get('menu/delete')

    def get_menu_info(self):
        """
        获取自定义菜单配置
        详情请参考
        http://mp.weixin.qq.com/wiki/17/4dc4b0514fdad7a5fbbd477aa9aab5ed.html

        :return: 返回的 JSON 数据包

        使用示例::

            from wechatpy import WeChatClient

            client = WeChatClient('appid', 'secret')
            menu_info = client.menu.get_menu_info()

        """
        return self._get('get_current_selfmenu_info')

    def add_conditional(self, menu_data):
        """
        创建个性化菜单 ::

            from wechatpy import WeChatClient

            client = WeChatClient("appid", "secret")
            client.menu.add_conditional({
                "button":[
                    {
                        "type":"click",
                        "name":"今日歌曲",
                        "key":"V1001_TODAY_MUSIC"
                    },
                    {
                        "type":"click",
                        "name":"歌手简介",
                        "key":"V1001_TODAY_SINGER"
                    },
                    {
                        "name":"菜单",
                        "sub_button":[
                            {
                                "type":"view",
                                "name":"搜索",
                                "url":"http://www.soso.com/"
                            },
                            {
                                "type":"view",
                                "name":"视频",
                                "url":"http://v.qq.com/"
                            },
                            {
                                "type":"click",
                                "name":"赞一下我们",
                                "key":"V1001_GOOD"
                            }
                        ]
                    }
                ],
                "matchrule":{
                  "group_id":"2",
                  "sex":"1",
                  "country":"中国",
                  "province":"广东",
                  "city":"广州",
                  "client_platform_type":"2"
                }
            })

        详情请参考
        http://mp.weixin.qq.com/wiki/0/c48ccd12b69ae023159b4bfaa7c39c20.html

        :param menu_data: Python 字典

        :return: 返回的 JSON 数据包
        """
        return self._post(
            'menu/addconditional',
            data=menu_data
        )

    def del_conditional(self, menu_id):
        """
        删除个性化菜单

        详情请参考
        http://mp.weixin.qq.com/wiki/0/c48ccd12b69ae023159b4bfaa7c39c20.html

        :param menu_id: 菜单ID

        :return: 返回的 JSON 数据包

        使用示例::

            from wechatpy import WeChatClient

            client = WeChatClient('appid', 'secret')
            res = client.menu.del_conditional('menu_id')

        """
        return self._post(
            'menu/delconditional',
            data={'menuid': menu_id}
        )

    def try_match(self, user_id):
        """
        测试个性化菜单匹配结果

        详情请参考
        http://mp.weixin.qq.com/wiki/0/c48ccd12b69ae023159b4bfaa7c39c20.html

        :param user_id: 可以是粉丝的OpenID,也可以是粉丝的微信号。

        :return: 该接口将返回菜单配置

        使用示例::

            from wechatpy import WeChatClient

            client = WeChatClient('appid', 'secret')
            res = client.menu.try_match('openid')

        """
        return self._post(
            'menu/trymatch',
            data={'user_id': user_id}
        )

只讲讲create那边。看到create需要传个menu_data参数进去,menu_data是微信所需要的菜单,要求格式已经列出了,是个python字典。

3、顺一下思路

在我django管理后台那边保存所设置的菜单的时候,将保存到数据库的菜单数据读取出来。然后按照微信所需要的格式进行输出个menu_data,将这个menu_data扔在wechatpy提供的create()中。

二、代码实现

其实也查过好些博客,但是很多都是直接将菜单的设置直接写在了代码中。而我这次需要可以在django的管理后台动态设置菜单,而不是说跑去代码那边改。

这是项目目录,只改动到wechat这个app(专门用来放微信开发功能的):

动到的文件有:admin.py、models.py、utils.py

wechat/models.py:

from account.models import MerchantModel

# 自定义菜单
# 一级菜单数组,个数应为1~3个,必须
# 二级菜单数组,个数应为1~5个,非必须
# type,name,key或者有子菜单的:name,sub_btn[]
# type,name,key/url
class WechatMenuModel(TimeStampedModel):
    # 菜单类型
    BUTTON_TYPE=(
        ('view','跳转网页'),
        ('click','发送消息'),
        ('miniprogram','跳转小程序')
    )
    # 当BUTTON_TYPE=="click"时的回复消息类型
    REPLY_MESSAGE_TYPE = (
        ('text', '文字'),
        ('media_id', '图片'),
        ('view', '图文消息'),
    )
    button = models.CharField(verbose_name='一级菜单',max_length=16, help_text='一级菜单可设置1~3个')
    sub_button = models.CharField(verbose_name='二级菜单',max_length=60, null=True, blank=True, help_text='二级菜单可设置1~5个')
    type = models.CharField(verbose_name='菜单类型',max_length=100,choices=BUTTON_TYPE)
    reply_type = models.CharField(verbose_name='回复的消息类型',max_length=100,choices=REPLY_MESSAGE_TYPE, null=True, blank=True)
    # 当type=="click"
    ## 文字回复
    key = models.CharField(verbose_name='文字回复', max_length=128, null=True, blank=True, help_text='菜单KEY值,用于消息接口推送')
    ## 图片回复
    media_id = models.CharField(verbose_name='图片回复', null=True, blank=True, max_length=1000, help_text='通过公众号上传多媒体文件,得到的id')
    ## 图文消息回复. view、miniprogram类型必须
    url = models.URLField(verbose_name='图文回复(点击图文消息跳转链接)',null=True,blank=True, max_length=1024, help_text='菜单类型为"跳转网页"或"跳转小程序"时必填')
    # 当type=="miniprogram"时必填
    appid = models.CharField(verbose_name='小程序appid', null=True, blank=True, max_length=1000, help_text='小程序的appid(仅认证公众号可配置). 菜单类型为"跳转小程序"时必填')
    pagepath = models.CharField(verbose_name='页面路径', null=True, blank=True, max_length=1000, help_text='小程序的页面路径. 菜单类型为"跳转小程序"时必填')

    merchant = models.ForeignKey(MerchantModel, verbose_name='商户')

    def __str__(self):
        return str(self.id)

    class Meta:
        ordering=('-created',)
        managed = True
        db_table = 't_wechat_custom_menu'
        app_label = 'wechat'
        verbose_name = u'自定义菜单'
        verbose_name_plural = u'自定义菜单'

此处说明一下,merchant是存放merchant_id的。merchant大概是这样子:

account/models.py:

class MerchantModel(TimeStampedModel):
    name = models.CharField(verbose_name='商户名称', max_length=100)
    appid = models.CharField(verbose_name='AppId', max_length=50, unique=True)
    app_secret = models.CharField(verbose_name='AppSecret', max_length=100)
    app_token = models.CharField(verbose_name='APPToken', max_length=128)
    encoding_aes_key = models.CharField(verbose_name='消息加解密密钥', max_length=128, null=True, blank=True)
    user = models.OneToOneField(User, verbose_name='商户账号')

    def __str__(self):
        return self.name

    class Meta:
        ordering = ('-created',)
        managed = True
        db_table = 't_account_merchant'
        app_label = 'account'
        verbose_name = u'商户信息'
        verbose_name_plural = u'商户信息'

wechat/utils.py:

专门放工具函数

from wechat.models import WechatMenuModel
from wechatpy import WeChatClient


def del_dict_null(dic):
	""""
	删除字典中的空值
	:param dic 要处理的字典
	"""
	for i in list(dic.keys()):
		if not dic[i]:
			del dic[i]


def del_repeat_dict(ls):
	""""
	删除列表中重复的字典
	:param ls 要处理的列表
	"""
	ls2 = list()
	ls2.append(ls[0])
	for dic in ls:
		k = 0
		for item in ls2:
			# print 'item'
			if dic['name'] != item['name']:
				k = k + 1
				# continue
			else:
				break
			if k == len(ls2):
				ls2.append(dic)
	return ls2


def menu_create(merchant):
	""""
	获取菜单数据,转换为字典,并创建菜单
	:param merchant 商户id
	"""
	button = []
	ls = WechatMenuModel.objects.filter(merchant=merchant).order_by('id')
	for i in range(len(ls)):  # 循环一级菜单
		if ls[i].sub_button is None:
			btn_dict = dict(name=ls[i].button, type=ls[i].type, key=ls[i].key, url=ls[i].url)
			del_dict_null(btn_dict)  # 去字典空值
			button.append(btn_dict)
		else:
			sub_ls = WechatMenuModel.objects.filter(merchant=merchant, button=ls[i].button).order_by('id')
			sub_button = []
			if sub_ls[0].button == ls[i].button:
				for k in range(len(sub_ls)):  # 循环二级菜单
					sub_btn_dict = dict(name=sub_ls[k].sub_button, type=sub_ls[k].type, key=sub_ls[k].key, url=sub_ls[k].url)
					del_dict_null(sub_btn_dict)
					sub_button.append(sub_btn_dict)
				btn_dict = dict(name=ls[i].button, sub_button=sub_button)
				button.append(btn_dict)
	new_button = del_repeat_dict(button)
	menu_data = {'button': new_button}
	client = WeChatClient(merchant.appid, merchant.app_secret)
	client.menu.create(menu_data)

最主要是menu_create(),它上面那些都是辅助函数。这里的两个for循环想了其实有点久。

循环获取所设置的菜单,字段名是按照微信的规则来的。button为一级菜单,sub_button为二级菜单。实际上有两种list,一种是父菜单button[{},{},{}],另一种是某个父菜单下的子菜单sub_button[{},{},{},{},{}]

数据库:

wechat/admin.py:

这里是关联到django的后台管理系统

from django.contrib import admin
from django.contrib import messages
from django.contrib.admin.templatetags.admin_urls import add_preserved_filters
from django.http import Http404
from django.http import HttpResponseRedirect
from wechatpy import WeChatClient
from wechatpy.client.api import WeChatCustomService
from wechat.utils import menu_create

from wechat.models import WechatMenuModel

class WechatMenuModelAdmin(admin.ModelAdmin):
    # 在后台管理系统的界面显示一级菜单名、二级菜单名、菜单类型、回复类型
    list_display = ('button', 'sub_button', 'type', 'reply_type', )
    # 可对二级菜单名进行搜索
    search_fields = ('sub_button',)
    list_fields = ('sub_button', 'reply_type')

    merchant_fields = (
        'button', 'sub_button', 'type', 'reply_type', 'key', 'media_id', 'url', 'appid', 'pagepath', )
    super_fields = (
        'button', 'sub_button', 'type', 'reply_type', 'key', 'media_id', 'url', 'appid', 'pagepath', 'merchant', )

    merchant_fieldsets = (
        (None, {'fields': ('button', 'sub_button', 'type', 'reply_type',)}),
        (u'文字回复', {'fields': (
            'key',
        )}),
        (u'图片回复', {'fields': (
            'media_id',
        )}),
        (u'图文回复', {'fields': (
            'url',
        )}),
        (u'小程序信息', {'fields': (
            'appid', 'pagepath',
        )}),
    ),
    super_fieldsets = (
        (None, {'fields': ('button', 'sub_button', 'type', 'reply_type', 'merchant')}),
        (u'文字回复', {'fields': (
            'key',
        )}),
        (u'图片回复', {'fields': (
            'media_id',
        )}),
        (u'图文回复', {'fields': (
            'url',
        )}),
        (u'小程序信息', {'fields': (
            'appid', 'pagepath',
        )}),
    )

    def get_fieldsets(self, request, obj=None):
        if request.user.is_superuser or request.user.groups.filter(name='Admin'):
            return self.super_fieldsets
        if hasattr(request.user, 'merchantmodel') and request.user.groups.filter(name='MerchantGroup'):
            return self.merchant_fieldsets
        else:
            return None

    def get_fields(self, request, obj=None):
        if request.user.is_superuser or request.user.groups.filter(name='Admin'):
            return self.super_fields
        if hasattr(request.user, 'merchantmodel') and request.user.groups.filter(name='MerchantGroup'):
            return self.merchant_fields
        else:
            return None

    def save_model(self, request, obj, form, change):
        if hasattr(request.user, 'merchantmodel') and request.user.groups.filter(name='MerchantGroup'):
            obj.merchant = request.user.merchantmodel
        if obj.type == 'click':
            if obj.reply_type is None or len(obj.reply_type.replace(' ', '')) == 0:
                messages.error(request, '保存失败:回复的消息内容不能为空')
            else:
                if obj.reply_type == 'text':
                    if obj.key is None or len(obj.key.replace(' ', '')) == 0:
                        messages.error(request, '保存失败:"文本回复正文"不能为空')
                    else:
                        obj.save()
                        menu_create(obj.merchant)
                elif obj.reply_type == 'media_id':
                    if obj.media_id is None or len(obj.media_id.replace(' ', '')) == 0:
                        messages.error(request, '保存失败:"图片媒体ID"不能为空')
                    else:
                        obj.save()
                        menu_create(obj.merchant)
        elif obj.type == 'view':
            if obj.url is None or len(obj.reply_type.replace(' ', '')) == 0:
                messages.error(request, '保存失败:图文回复的跳转链接不能为空')
            else:
                obj.save()
                menu_create(obj.merchant)
        elif obj.type == 'miniprogram':
            if obj.appid is None or obj.pagepath is None or obj.url is None:
                messages.error(request, '保存失败:小程序appid, 小程序的页面路径,图文回复的跳转链接不能为空')
            else:
                obj.save()
                menu_create(obj.merchant)

    def get_queryset(self, request):
        qs = super(WechatMenuModelAdmin, self).get_queryset(request)
        if request.user.is_superuser or request.user.groups.filter(name='Admin'):
            return qs
        if hasattr(request.user, 'merchantmodel') and request.user.groups.filter(name='MerchantGroup'):
            return qs.filter(merchant=request.user.merchantmodel)
        else:
            raise Http404(u'您没有该权限,请联系管理员!')

    def response_add(self, request, obj, post_url_continue=None):
        opts = obj._meta
        preserved_filters = self.get_preserved_filters(request)
        if hasattr(request, 'err') and request.err:
            self.message_user(request, request.err_msg, messages.ERROR)
            redirect_url = request.path
            redirect_url = add_preserved_filters({'preserved_filters': preserved_filters, 'opts': opts}, redirect_url)
            return HttpResponseRedirect(redirect_url)
        else:
            return super(WechatMenuModelAdmin, self).response_add(request, obj, post_url_continue)

    def response_change(self, request, obj):
        opts = self.model._meta
        preserved_filters = self.get_preserved_filters(request)
        if hasattr(request, 'err') and request.err:
            self.message_user(request, request.err_msg, messages.ERROR)
            redirect_url = request.path
            redirect_url = add_preserved_filters({'preserved_filters': preserved_filters, 'opts': opts}, redirect_url)
            return HttpResponseRedirect(redirect_url)
        else:
            return super(WechatMenuModelAdmin, self).response_change(request, obj)


# 将WechatMenuModel与WechatMenuModelAdmin
admin.site.register(WechatMenuModel, WechatMenuModelAdmin)

上面主要是save_model()下的

obj.save()

menu_create(obj.merchant)

用以完成对微信自定义菜单功能的实现。

服务器上跑一下,是前面几张图的效果。基本达到所需

感觉是写得不够优雅的,有更好的想法的话欢迎留言!

发布了27 篇原创文章 · 获赞 9 · 访问量 5700

猜你喜欢

转载自blog.csdn.net/weixin_38604274/article/details/99864019