目录

实现的效果图

# 图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_literalsfrom wechatpy.exceptions import WeChatClientException
from wechatpy.client.api.base import BaseWeChatAPIclass WeChatMenu(BaseWeChatAPI):def get(self):"""查询自定义菜单。详情请参考http://mp.weixin.qq.com/wiki/16/ff9b7b85220e1396ffa16794a9d95adc.html:return: 返回的 JSON 数据包使用示例::from wechatpy import WeChatClientclient = WeChatClient('appid', 'secret')menu = client.menu.get()"""try:return self._get('menu/get')except WeChatClientException as e:if e.errcode == 46003:# menu not existreturn Noneelse:raise edef create(self, menu_data):"""创建自定义菜单 ::from wechatpy import WeChatClientclient = 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 WeChatClientclient = 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 WeChatClientclient = 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 WeChatClientclient = 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 WeChatClientclient = 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 WeChatClientclient = 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 WeChatClientclient = 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 = Truedb_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.nameclass Meta:ordering = ('-created',)managed = Truedb_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 WeChatClientdef 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 = 0for item in ls2:# print 'item'if dic['name'] != item['name']:k = k + 1# continueelse:breakif k == len(ls2):ls2.append(dic)return ls2def 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_createfrom wechat.models import WechatMenuModelclass 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_fieldsetsif hasattr(request.user, 'merchantmodel') and request.user.groups.filter(name='MerchantGroup'):return self.merchant_fieldsetselse:return Nonedef get_fields(self, request, obj=None):if request.user.is_superuser or request.user.groups.filter(name='Admin'):return self.super_fieldsif hasattr(request.user, 'merchantmodel') and request.user.groups.filter(name='MerchantGroup'):return self.merchant_fieldselse:return Nonedef save_model(self, request, obj, form, change):if hasattr(request.user, 'merchantmodel') and request.user.groups.filter(name='MerchantGroup'):obj.merchant = request.user.merchantmodelif 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 qsif 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._metapreserved_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.pathredirect_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._metapreserved_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.pathredirect_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)

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

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

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

django学习 | 实战 # 微信自定义菜单相关推荐

  1. 微信自定义菜单java_使用Java语言开发微信公众平台(八)——自定义菜单功能

    随着上一篇文章的结束,我们已经实现了所有消息的类型的回复功能.今天,我们来学习更加高大上,也更加重要的自定义菜单功能. 一.了解自定义菜单 自定义菜单是微信公众平台最常用也是最重要的功能之一.根据微信 ...

  2. Magicodes.WeiChat——自定义knockoutjs template、component实现微信自定义菜单

    2019独角兽企业重金招聘Python工程师标准>>> 本人一向比较喜欢折腾,玩了这么久的knockoutjs,总觉得不够劲,于是又开始准备折腾自己了. 最近在完善Magicodes ...

  3. 微信java创建菜单_微信自定义菜单的创建(JAVA版)

    微信自定义菜单的创建与使用 需求 当微信公众号设置为开发者模式,想要自己创建和开发菜单的话,就需要自己调用微信创建菜单的接口来创建菜单了.创建菜单之后,如果需要迫切的看到效果,有时候需要取消关注-从新 ...

  4. Magcodes.WeiChat——自定义CustomCreationConverter之实现微信自定义菜单的序列化

    微信自定义菜单接口是一个比较麻烦的接口,往往开发的小伙伴们看到下面的这段返回JSON,整个人就会不好了: {"menu":{"button":[{"t ...

  5. 微信自定义菜单扩容?

    在微信公众账号"每日资讯"中,自定义菜单右侧出现了一个箭头图标,这是否意味着微信自定义菜单打算扩展?

  6. 微信自定义菜单与网页授权结合

    用Java开发,微信自定义菜单的URL例如 即进入网页授权  网页静默授权  可以获取用户信息 ,此处可以根据点击的菜单进入相应的菜单界面,在界面中涉及到JS-SDK的使用,步骤:生成JSSDK签名步 ...

  7. java 微信自定义菜单 java微信接口开发 公众平台 SSM

    1.   权限管理:点开二级菜单进入三级菜单显示 角色(基础权限)和按钮权限       角色(基础权限): 分角色组和角色,独立分配菜单权限和增删改查权限.       按钮权限: 给角色分配按钮权 ...

  8. java 微信自定义菜单 java微信接口开发 公众平台 SSM redis shiro 多数据源

    A 调用摄像头拍照,自定义裁剪编辑头像,头像图片色度调节 B 集成代码生成器 [正反双向](单表.主表.明细表.树形表,快速开发利器)+快速表单构建器 freemaker模版技术 ,0个代码不用写,生 ...

  9. 微信自定义菜单 有代码生成器 java微信接口开发 公众平台 SSM redis shiro 多数据源...

    获取[下载地址]   QQ: 313596790 官网 http://www.fhadmin.org/ A 调用摄像头拍照,自定义裁剪编辑头像,头像图片色度调节 B 集成代码生成器 [正反双向](单表 ...

最新文章

  1. python画笔初始位置_turtle绘画-移动落笔点(改变初始原点)
  2. GitHub优秀开源项目收集
  3. Android init.rc文件解析过程详解(二)
  4. Redis的Expire与Setex
  5. 肝了这200页!火爆全网的Python学习知识手册!(附下载)
  6. Android中RelativeLayout各个属性的含义
  7. [刷题]算法竞赛入门经典(第2版) 6-7/UVa804 - Petri Net Simulation
  8. Java 8 Optional 类 学习
  9. 简述php的发展和特点,有关PHP特点的详细介绍
  10. TensorFlow 学习(十一)—— 正则(regularizer)
  11. 使用paramiko在eNSP的交换机中批量创建VLAN
  12. windows 64位sed.exe_32位,64位,x86,x64到底是什么关系?差距居然这么大
  13. angularJs内置指令63个
  14. php excel图表,简易的phpexcel导出柱状图
  15. 如何设置无线路由器?
  16. 学习python的摸鱼日常
  17. 期货开户客户经理一对一专业服务指导
  18. 深度linux安装cad,在deepin下安装CAD — 原生CAD 看图画图
  19. Matlab小结6(线性规划)
  20. bilibili直播地址获取

热门文章

  1. 国美金融是“持牌大户”还是“违规大户”:国美易卡贷超出现套路贷APP,暴力催收恐吓借款人
  2. 数据库迁移expdp impdp 与 OGg 搭建
  3. java制作闪星星_【治水】怎么用java画各种星星组成的图形
  4. 华为路由器显示网络未连接到服务器,华为路由器Q1连接没有网络该怎么办
  5. 文件 服务器属性,去除服务器文件上的RHS属性
  6. 第一节、linux中安装redis(一)
  7. Qt实现窗口轮播效果
  8. MySql学习笔记(2)--数据库操作及数据管理
  9. 2022-2028全球2-氨基-5-甲基吡啶行业调研及趋势分析报告
  10. JVM性能优化一些概念简介