models.py 36 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097
  1. # -*- coding: utf-8 -*-
  2. # !/usr/bin/env python
  3. """
  4. web.ad.models
  5. ~~~~~~~~~ad
  6. """
  7. import logging
  8. import random
  9. from collections import OrderedDict
  10. import datetime
  11. import simplejson as json
  12. import sys
  13. import user_agents
  14. from aliyunsdkcore.client import AcsClient
  15. from aliyunsdkunimkt.request.v20181212 import RegistDeviceRequest, ChargeLaunchRequest
  16. from bson.objectid import ObjectId
  17. from django.conf import settings
  18. from django.contrib.auth.hashers import make_password
  19. from django.utils.module_loading import import_string
  20. from mongoengine import StringField, DateTimeField, BooleanField, ListField, DoesNotExist, \
  21. IntField, FloatField, DictField, EmbeddedDocument, DynamicDocument
  22. from typing import Union, List, AnyStr, Dict, Any, Optional, TYPE_CHECKING
  23. from apilib.utils_datetime import today_format_str, yesterday_format_str
  24. from apps import serviceCache
  25. from apps.thirdparties.aliyun import AlipayYunMaV3
  26. from apps.web.ad import AdUser
  27. from apps.web.common.models import UserSearchable, District
  28. from apps.web.constant import Const, AdType, AdSpace
  29. from apps.web.core.db import CustomizedSequenceField
  30. from apps.web.core.db import Searchable
  31. from apps.web.device.models import Device
  32. from apps.web.utils import CustomizedValidationError, detect_app_from_ua, is_user
  33. logger = logging.getLogger(__name__)
  34. if TYPE_CHECKING:
  35. from apps.web.dealer.models import Dealer
  36. from apps.web.user.models import MyUser
  37. from apps.web.device.models import DeviceDict
  38. def nullable(obj, nil=''):
  39. class _(object):
  40. def __getattr__(self, item): return nil
  41. if obj is None:
  42. return _()
  43. else:
  44. return obj
  45. class Advertiser(UserSearchable):
  46. """
  47. 广告主
  48. """
  49. balance = FloatField(verbose_name="余额", default=0)
  50. #: 额度由厂商配置
  51. quota = IntField(verbose_name="残留额度", default=0)
  52. #: 该属性由厂商配置,限定广告商可能投放的范围,另一些支撑场景为代理商
  53. devCondition = ListField(verbose_name="选取广告设备的过滤条件",
  54. default=[{"selectAgents": [], "selectDealers": [], "selectGroups": []}])
  55. companyName = StringField(verbose_name="公司名称", default="")
  56. managerId = StringField(verbose_name="所属厂商ID", default="")
  57. meta = {
  58. 'collection': 'advertisers',
  59. 'db_alias': 'default',
  60. 'indexes': [
  61. {
  62. 'fields': ['username'],
  63. 'unique': True
  64. }
  65. ],
  66. }
  67. search_fields = ('username', 'nickname', 'remarks')
  68. def to_dict(self):
  69. rv = super(Advertiser, self).to_dict()
  70. rv.update({
  71. 'id': str(self.id),
  72. 'balance': self.balance,
  73. 'quota': self.quota,
  74. 'devCondition': self.devCondition,
  75. 'companyName': self.companyName
  76. })
  77. return rv
  78. @property
  79. def preAllocatedDeviceConditions(self):
  80. return \
  81. [
  82. {
  83. 'agentIdList': [_['id'] for _ in cond['selectAgents']],
  84. 'dealerIdList': [_['dealerId'] for _ in cond['selectDealers']],
  85. 'groupIdList': [_['id'] for _ in cond['selectGroups']]
  86. }
  87. for cond in self.devCondition
  88. ]
  89. def recharge(self, amount): return self.update(inc__balance=amount)
  90. class AdvertisementConfig(EmbeddedDocument):
  91. """
  92. 广告配置
  93. """
  94. offlineFansNumber = IntField(verbose_name='每日最多粉丝量', default=-1)
  95. class Advertisement(UserSearchable):
  96. """
  97. ..modified 2018/05/24
  98. 目前广告模式
  99. """
  100. #: 广告基本属性
  101. name = StringField(verbose_name="广告名称", max_length=100)
  102. adType = StringField(verbose_name="广告的类型", default="")
  103. adSpace = StringField(verbose_name="广告位", default="payAfter")
  104. adId = CustomizedSequenceField(verbose_name="内置数字自增ID", value_decorator=lambda _: int(_) + 100000)
  105. managerId = StringField(verbose_name="管理员ID", null=False)
  106. startTime = DateTimeField(verbose_name="开始时间", default=datetime.datetime.now)
  107. endTime = DateTimeField(verbose_name="结束时间", default=datetime.datetime.now() + datetime.timedelta(days=365))
  108. groupName = StringField(verbose_name="商户名称", default="")
  109. provinceId = StringField(verbose_name="省份ID", default="")
  110. cityId = StringField(verbose_name="城市ID", default="")
  111. areaId = StringField(verbose_name="区域ID", default="")
  112. address = StringField(verbose_name="地址", default="", max_length=200)
  113. addressType = StringField(verbose_name="地址类型", default="")
  114. #: 广告创意
  115. img = StringField(verbose_name="图片url", default="")
  116. link = StringField(verbose_name="定向链接", default="")
  117. word = StringField(verbose_name="广告词", max_length=100)
  118. script = StringField(verbose_name="插入脚本文本段")
  119. scriptType = StringField(verbose_name='脚本类型')
  120. showType = StringField(verbose_name=u"展现方式", default="jump")
  121. #: 广告过滤器
  122. #:: 面向投放环境的过滤器(目前为设备)
  123. publishers = ListField(verbose_name="特定的发布主 == 经销商", default=['*'])
  124. agents = ListField(verbose_name="代理商", default=[])
  125. addressTypeList = ListField(verbose_name="地址类型的列表", default=[])
  126. devTypeList = ListField(verbose_name="设备类型的列表", default=[])
  127. devCondition = ListField(verbose_name="设备的过滤条件",
  128. default=[{"selectAgents": [], "selectDealers": [], "selectGroups": []}])
  129. #:: 面向用户的广告过滤器
  130. targetSex = StringField(verbose_name="定位投放性别", default='*')
  131. targetPhoneOS = ListField(verbose_name="用户手持终端", default=[])
  132. gateway = ListField()
  133. #: 接受广告投递的设备
  134. #: `example` [{"logicalCode" : "32320", "devTypeName" : "纸巾机"}]
  135. devList = ListField(verbose_name="接受广告投递的设备", default=[])
  136. #: 定价
  137. price = FloatField(verbose_name="推送一次的价格", default=0.0)
  138. agentPrice = FloatField(verbose_name="推送一次的代理商分成价格", default=0.0)
  139. dealerPrice = FloatField(verbose_name="推送一次的经销商分成价格", default=0.0)
  140. #: 配置项
  141. configs = DictField(verbose_name="配置", default={})
  142. offlineFansNumber = IntField(verbose_name='每日最多粉丝量', default=-1)
  143. PIN = StringField(verbose_name="广告主登录密码", default='1234')
  144. fansType = StringField(verbose_name="吸粉类型", default='official')
  145. advertiserId = StringField(verbose_name="广告主ID", default='')
  146. online = BooleanField(verbose_name=u"广告是否上线", default=False)
  147. #: 为了确保不产生脏数据,广告的删除并不做实质的删除,以后随着数据量增加,可考虑转储
  148. deleted = BooleanField(verbose_name="是否删除的flag", default=False)
  149. deletedTime = DateTimeField()
  150. meta = {
  151. "collection": "ads",
  152. "db_alias": "default",
  153. 'indexes': [
  154. 'advertiserId',
  155. 'managerId',
  156. 'adId'
  157. ]
  158. }
  159. search_fields = ('word', 'adId', 'name', 'link')
  160. def __str__(self):
  161. return 'Advertisement<id={} adId={}>'.format(str(self.id), self.adId)
  162. def count_cache_key(self, date=None):
  163. if date is None:
  164. date = datetime.datetime.now().strftime(Const.DATE_FMT)
  165. return 'adId-{adId}-count-{date}'.format(adId=self.adId, date=date)
  166. def is_authenticated(self):
  167. return True
  168. def set_password(self, raw_password):
  169. self.password = make_password(raw_password)
  170. self.save()
  171. return self
  172. def check_password(self, raw_password):
  173. return raw_password == self.password
  174. @staticmethod
  175. def get_md5_password(raw_password):
  176. return make_password(raw_password)
  177. @classmethod
  178. def get_available_ads(cls, managerId=None, **kwargs):
  179. """
  180. only return un-deleted ads
  181. :param managerId:
  182. :return:
  183. """
  184. if managerId:
  185. return cls.objects(managerId=managerId, deleted=False, **kwargs)
  186. else:
  187. return cls.objects(deleted=False, **kwargs)
  188. @classmethod
  189. def filter_pay_after(cls, **kwargs):
  190. """
  191. :param kwargs:
  192. :return:
  193. """
  194. return cls.get_available_ads(adSpace=AdSpace.PAYAFTER, endTime__gte=datetime.datetime.now(), **kwargs)
  195. @classmethod
  196. def filter_by_top_show(cls, **kwargs):
  197. """
  198. :param kwargs:
  199. :return:
  200. """
  201. return cls.get_available_ads(adSpace=AdSpace.TOPSHOW, showType=AdType.BANNER, **kwargs)
  202. @classmethod
  203. def get_by_adId(cls, adId):
  204. # type: (int) -> Union[Advertisement,None]
  205. if adId is None:
  206. return None
  207. return cls.objects(adId=int(adId), deleted=False).first()
  208. @classmethod
  209. def get_by_ids(cls, ids):
  210. return cls.objects.filter(id__in=ids)
  211. def flag_deleted(self):
  212. return self.update(deleted=True, deletedTime=datetime.datetime.now())
  213. def to_dict(self):
  214. rv = super(Advertisement, self).to_dict()
  215. if ObjectId.is_valid(self.advertiserId):
  216. advertiser = Advertiser.objects(id=self.advertiserId).first()
  217. if advertiser is None:
  218. logger.warn(u'广告主未找到, adId=(%d), advertiserId(%s)' % (self.adId, self.advertiserId,))
  219. advertiserInfo = ''
  220. else:
  221. advertiserInfo = u'%s(%s)额度[%d]' % (advertiser.nickname, advertiser.username, advertiser.quota)
  222. else:
  223. advertiserInfo = ''
  224. rv.update({
  225. #: 广告基本属性
  226. 'id': str(self.id),
  227. 'name': self.name,
  228. 'adType': self.adType,
  229. 'img': self.img,
  230. 'word': self.word,
  231. 'link': self.link,
  232. 'adId': self.adId,
  233. 'startTime': self.startTime.strftime("%Y-%m-%d %H:%M"),
  234. 'endTime': self.endTime.strftime("%Y-%m-%d %H:%M"),
  235. # 广告主
  236. 'advertiserInfo': advertiserInfo,
  237. #: 过滤器
  238. 'devCondition': self.devCondition,
  239. 'devTypeList': self.devTypeList,
  240. 'addressTypeList': self.addressTypeList,
  241. #: 接受广告投递的设备
  242. 'devList': self.devList,
  243. #:: 面向用户的广告过滤器
  244. 'targetPhoneOS': self.targetPhoneOS,
  245. 'targetSex': self.targetSex,
  246. #: 定价
  247. 'price': self.price,
  248. 'agentPrice': self.agentPrice,
  249. 'dealerPrice': self.dealerPrice,
  250. 'online': self.online,
  251. #: 配置项
  252. 'configs': self.configs,
  253. 'PIN': self.PIN,
  254. 'login': False,
  255. 'loginUsername': '',
  256. 'fansType': self.fansType,
  257. 'offlineFansNumber': self.offlineFansNumber,
  258. 'script': self.script,
  259. 'scriptType': self.scriptType
  260. })
  261. return rv
  262. @property
  263. def allocated_device_logicalCodes(self):
  264. return [_['logicalCode'] for _ in self.devList]
  265. @property
  266. def allocations(self):
  267. # type: ()->Dict[str, List[str]]
  268. allocation_map = {'agent': [], 'dealer': [], 'group': [], 'device': self.allocated_device_logicalCodes}
  269. for cond in self.devCondition:
  270. allocation_map['agent'].extend([_['id'] for _ in cond['selectAgents']])
  271. allocation_map['dealer'].extend([_['dealerId'] for _ in cond['selectDealers']])
  272. allocation_map['group'].extend([_['id'] for _ in cond['selectGroups']])
  273. return allocation_map
  274. def to_json(self):
  275. return json.dumps(self.to_dict())
  276. def to_redis(self):
  277. return
  278. def clean(self):
  279. if self.startTime > self.endTime:
  280. raise CustomizedValidationError(u'开始时间必须大于结束时间')
  281. @property
  282. def proxy(self):
  283. # return AdProxy(self.adId)
  284. return
  285. def set_proxy(self):
  286. # return AdProxy(self.adId, **self.to_dict()).to_redis()
  287. return
  288. def set_offline(self):
  289. return self.update(status=False)
  290. #: 目前的规则做简单一点,每天一次的
  291. @staticmethod
  292. def is_show_huawei_auth_web(openId):
  293. ads = AdRecord.objects.filter(openId=openId, adId=0)
  294. if ads.count() > 0:
  295. return False
  296. return True
  297. def match(self, feature, against):
  298. if feature == 'sex':
  299. if self.targetSex == '*' or not self.targetSex:
  300. return True
  301. else:
  302. # TODO to remove, to change data to english
  303. _ = {u'男': 'male', u'女': 'female'}
  304. return _[self.targetSex] == against
  305. elif feature == 'phoneOS':
  306. if not self.targetPhoneOS:
  307. return True
  308. else:
  309. return against in self.targetPhoneOS
  310. elif feature == 'gateway':
  311. if not self.gateway:
  312. return True
  313. else:
  314. return against in self.gateway
  315. @property
  316. def selected_user_feature_keys(self):
  317. return ['phoneOS', 'sex', 'gateway']
  318. @property
  319. def selected_user_feature_map(self):
  320. return {_: getattr(self, _) for _ in self.selected_user_feature_keys if getattr(self, _)}
  321. def match_user(self, user):
  322. """
  323. 广告匹配用户
  324. :param user:
  325. :return:
  326. :rtype: bool
  327. """
  328. #: 理论上来讲匹配用户的特征应该是用户本身特征的子集
  329. if not set(self.selected_user_feature_keys).issubset(user.feature_keys):
  330. return False
  331. #: 去除所有空项,两者特征吻合,即配对成功
  332. else:
  333. for feature, against in user.feature_map.iteritems():
  334. if not self.match(feature, against):
  335. return False
  336. return True
  337. @property
  338. def ad_show_dict(self):
  339. """
  340. 获取广告信息. link字段是和以前的版本兼容, 后续修改后可以去掉
  341. :return:
  342. """
  343. rv = {'adShow': 'show', 'showType': self.showType, 'url': '', 'link': ''}
  344. if self.showType == 'jump':
  345. rv.update({'url': self.link, 'title': self.word})
  346. elif self.showType == 'float':
  347. rv.update({'img': self.img, 'url': self.link, 'title': self.word})
  348. elif self.showType == 'banner':
  349. rv.update({'img': self.img, 'url': self.link})
  350. elif self.showType == 'floatJump':
  351. rv.update({'img': self.img, 'url': self.link, 'title': self.word})
  352. return rv
  353. @classmethod
  354. def get_ali_cpm_url_v3(cls, adUser, logicalCode):
  355. # type: (AdUser, str)->Optional[dict]
  356. openId = adUser.openId
  357. if not openId:
  358. return None
  359. if not logicalCode:
  360. return None
  361. dev = Device.get_dev_by_logicalCode(logicalCode)
  362. if not dev:
  363. return None
  364. devObj = DevRegToAli.objects(logicalCode=logicalCode).first()
  365. if not devObj:
  366. regResult = DevRegToAli.reg_to_aliyun_v3(dev)
  367. if not regResult:
  368. return None
  369. try:
  370. result = AlipayYunMaV3().get_cpm_body(openId, logicalCode)
  371. logger.info(result)
  372. if result.body.success == True:
  373. # # 广告添加日志
  374. # try:
  375. # adLog = AdLog()
  376. # adLog.bidid = result.body.result.bidid
  377. # adLog.version = '3.0'
  378. # adLog.responseData = json.dumps(result.body.result.to_map(), ensure_ascii=False)
  379. # adLog.name = 'alipay_cpm'
  380. # adLog.adType = 'payafter'
  381. # adLog.openId = adUser.openId
  382. # adLog.logicalCode = logicalCode
  383. # adLog.gateway = 'alipay'
  384. # adLog.save()
  385. # except:
  386. # logger.error(traceback.format_exc())
  387. if len(result.body.result.seatbid[0].bid[0].ads):
  388. curl = result.body.result.seatbid[0].bid[0].ads[0].crurl
  389. return curl
  390. except Exception as e:
  391. logger.exception(e)
  392. @classmethod
  393. def get_pay_after_ad(cls, adUser, **kwargs):
  394. # type: (AdUser, dict)->dict
  395. ads = cls.filter_pay_after(**kwargs)
  396. for ad in ads: # type: Advertisement
  397. if ad.match_user(adUser):
  398. ad_dict = ad.ad_show_dict
  399. AdStatistics.inc_make_count(
  400. adSpace=AdSpace.PAYAFTER,
  401. showType='payAfter_{}'.format(ad_dict['showType']), gateway=adUser.feature_map['gateway'])
  402. return ad_dict
  403. return None
  404. @classmethod
  405. def get_ruhui_url(cls, adUser, dealer, logicalCode):
  406. if 'disable_ruhui_after_pay' in dealer.features:
  407. logger.debug('user<openid={}> get ruhui url for dev<l={}> is disable'.format(adUser.openId, logicalCode))
  408. return None
  409. from apps.web.user.utils import RedpackBuilder
  410. ruhui = RedpackBuilder.get_alipay_cpa_by_ruhui(openId=adUser.openId, logicalCode=logicalCode, showType='floatRedpack')
  411. if ruhui:
  412. ad_dict = {
  413. 'adShow': 'show',
  414. 'url': ruhui['url'],
  415. 'showType': 'floatRedpack',
  416. 'img': 'https://resource.washpayer.com/ad/redpack2.png',
  417. 'title': '点击入会天猫领取红包',
  418. }
  419. logger.debug(
  420. 'user<openid={}> get ruhui url for dev<l={}> is {}'.format(adUser.openId, logicalCode, ad_dict))
  421. AdStatistics.inc_make_count(adSpace=AdSpace.PAYAFTER, showType='payAfter_ruhui', gateway='alipay')
  422. return ad_dict
  423. else:
  424. logger.debug('user<openid={}> get ruhui url for dev<l={}> is null'.format(adUser.openId, logicalCode))
  425. return None
  426. @classmethod
  427. def get_default_ad_img(cls, showType):
  428. if showType == 'jump':
  429. return None
  430. elif showType in ['floatJump', 'float']:
  431. return 'https://resource.washpayer.com/ad/payafter_float_jump_1.png'
  432. else:
  433. return 'https://resource.washpayer.com/ad/payafter_banner_2.png'
  434. @classmethod
  435. def _fetch_payafter_ad(cls, ua, user, dealer, device=None):
  436. # type: (str, MyUser, Dealer, DeviceDict)->Optional[dict]
  437. try:
  438. gateway = detect_app_from_ua(ua)
  439. phoneOS = user_agents.parse(ua).os.family
  440. if gateway == 'alipay':
  441. showType = 'jump'
  442. else:
  443. if dealer.ad_show == 'show':
  444. showType = 'jump'
  445. else:
  446. showType = dealer.ad_show
  447. if is_user(user):
  448. adUser = AdUser(
  449. openId=user.openId,
  450. feature_keys=['phoneOS', 'sex', 'gateway'],
  451. feature_map={'phoneOS': phoneOS, 'sex': user.sex_in_en, 'gateway': gateway})
  452. else:
  453. adUser = AdUser(
  454. openId=None,
  455. feature_keys=['phoneOS', 'sex', 'gateway'],
  456. feature_map={'phoneOS': phoneOS, 'sex': 'male', 'gateway': gateway})
  457. if gateway == 'alipay' and device:
  458. support_ali = serviceCache.get('supportAliAd')
  459. if support_ali is None or support_ali != 'no':
  460. support_ali = True
  461. else:
  462. support_ali = False
  463. if support_ali:
  464. ali_cmp_url = cls.get_ali_cpm_url_v3(adUser=adUser, logicalCode=device.logicalCode)
  465. if ali_cmp_url:
  466. img = cls.get_default_ad_img(showType)
  467. ad_dict = {
  468. 'adShow': 'show',
  469. 'url': ali_cmp_url,
  470. 'showType': showType,
  471. 'title': '<div class="title">即将自动转到广告页面</div><div class="sub-title">请谨慎交易!如遇欺诈广告请联系平台投诉</div>'
  472. }
  473. if img:
  474. ad_dict.update({'img': img})
  475. return ad_dict
  476. return cls.get_pay_after_ad(adUser=adUser, showType=showType)
  477. except Exception as e:
  478. logger.exception(e)
  479. return None
  480. @classmethod
  481. def fetch_payafter_ad(cls, ua, user, dealer, device=None):
  482. # type: (str, MyUser, Dealer, DeviceDict)->Optional[dict]
  483. ad_dict = None
  484. try:
  485. while True:
  486. gateway = detect_app_from_ua(ua)
  487. if gateway == 'alipay':
  488. pass
  489. # ad_dict = cls.get_ruhui_url(user, dealer, device.logicalCode)
  490. # if ad_dict:
  491. # break
  492. else:
  493. if dealer.ad_show == 'noshow':
  494. logger.debug('dealer<id={}> close ad.'.format(str(dealer.id)))
  495. break
  496. if device:
  497. now = datetime.datetime.now()
  498. if device.disableADExpireDate and device.disableADExpireDate >= now:
  499. break
  500. ad_dict = cls._fetch_payafter_ad(ua, user, dealer, device)
  501. break
  502. if not ad_dict:
  503. AdStatistics.inc_make_count(adSpace=AdSpace.PAYAFTER, showType='payAfter_noshow', gateway=gateway)
  504. ad_dict = {
  505. 'adShow': 'noshow'
  506. }
  507. return ad_dict
  508. except Exception as e:
  509. logger.exception(e)
  510. return None
  511. class AliAdLog(Searchable):
  512. uid = StringField(verbose_name=u"用户标识")
  513. uidType = StringField(verbose_name=u"用户标识类型")
  514. campaignId = StringField(verbose_name=u"⼴告计划ID")
  515. bid = StringField(verbose_name=u"链路ID")
  516. tradeTS = IntField(verbose_name = u'交易时间')
  517. commission = StringField(verbose_name = u'参考收入')
  518. meta = {
  519. 'collection': 'ali_ad_log',
  520. 'db_alias': 'logdata',
  521. }
  522. AD_RECORD_FILTER_FIELDS = ['adId',
  523. 'openId',
  524. 'groupId',
  525. 'devNo',
  526. 'dealerId',
  527. 'agentId',
  528. 'devNo',
  529. 'managerId',
  530. 'advertiserId'] # type: List[AnyStr]
  531. class AdRecord(Searchable):
  532. adType = StringField()
  533. clicks = IntField(verbose_name="点击次数", default=0)
  534. shows = IntField(verbose_name="展示次数", default=0)
  535. rewarded = BooleanField(verbose_name="是否领取奖励成功", default=False)
  536. converted = BooleanField(verbose_name="是否转换成功", default=False)
  537. agentPrice = IntField(verbose_name='代理商广告价格')
  538. dealerPrice = IntField(verbose_name='经销商广告价格')
  539. price = IntField(verbose_name='广告价格')
  540. adName = StringField(verbose_name='广告名称')
  541. agentName = StringField(verbose_name='代理商名称')
  542. dealerName = StringField(verbose_name='经销商名称')
  543. nickname = StringField(verbose_name='用户昵称')
  544. sex = IntField(verbose_name='用户性别')
  545. devType = StringField(verbose_name='设备类型')
  546. address = StringField(verbose_name='设备所在地址')
  547. groupName = StringField(verbose_name='设备所在组名称')
  548. #: filters
  549. adId = IntField(verbose_name="广告ID")
  550. openId = StringField(verbose_name="点击者微信ID", default="")
  551. groupId = StringField(verbose_name="设备地址编号", default="")
  552. dealerId = StringField(verbose_name="经销商ID", default="")
  553. agentId = StringField(verbose_name="代理商ID", default="")
  554. devNo = StringField(verbose_name="设备号", default="")
  555. logicalCode = StringField(verbose_name="设备逻辑编码", default="")
  556. managerId = StringField(verbose_name="厂商ID", default="")
  557. advertiserId = StringField(verbose_name="广告主ID", default="")
  558. dateTimeAdded = DateTimeField(default=datetime.datetime.now, verbose_name='添加进来的时间')
  559. createdDate = StringField(verbose_name='添加日期',
  560. default=lambda: datetime.datetime.now().strftime(Const.DATE_FMT))
  561. dIncome = FloatField(verbose_name="经销商收入", default=0.0)
  562. aIncome = FloatField(verbose_name="代理商收入", default=0.0)
  563. mIncome = FloatField(verbose_name="厂家收入", default=0.0)
  564. features = DictField(verbose_name="额外特征", default={})
  565. remark = StringField(verbose_name="备注", default='')
  566. meta = {
  567. 'collection': 'ad_records',
  568. 'db_alias': 'logdata',
  569. 'indexes': [
  570. #: filters
  571. 'adId',
  572. 'openId',
  573. 'groupId',
  574. 'devNo',
  575. 'dealerId',
  576. 'agentId',
  577. 'devNo',
  578. 'managerId',
  579. 'advertiserId',
  580. #: date
  581. 'createdDate',
  582. '-dateTimeAdded',
  583. #: unique key
  584. {'fields': ['adId', 'openId'], 'unique': True}
  585. ]
  586. # ,'shard_key':('openId',)
  587. }
  588. def __repr__(self): return '<AdRecord adId=%s, openId=%s>' % (self.adId, self.openId)
  589. @classmethod
  590. def get_converted_records(cls, **kwargs):
  591. return cls.objects(converted=True, **kwargs)
  592. @classmethod
  593. def filter_pay_after(cls, **kwargs):
  594. return cls.objects(adType=AdType.PAY_AFTER, **kwargs)
  595. def income_for_whom(self, whom):
  596. _ = {
  597. 'dealer': self.dIncome,
  598. 'agent': self.aIncome,
  599. 'manager': self.mIncome
  600. }
  601. return _.get(whom)
  602. @staticmethod
  603. def filter_fields(): return AD_RECORD_FILTER_FIELDS
  604. def convert(self): return self.update(converted=True)
  605. def reward(self): return self.update(rewarded=True)
  606. @property
  607. def amount(self): return 0
  608. @property
  609. def dealer(self):
  610. Dealer = import_string('apps.web.dealer.models.Dealer')
  611. return Dealer.objects(self.dealerId).get()
  612. @classmethod
  613. def get_by_user_and_adId(cls, openId, adId): return cls.objects(openId=openId, adId=adId).first()
  614. @classmethod
  615. def user_today_already_rewarded(cls, openId):
  616. """
  617. 用户受限一天只能参与一次广告活动
  618. :param openId:
  619. :return:
  620. """
  621. todayDate = datetime.datetime.now().strftime(Const.DATE_FMT)
  622. return cls.objects(openId=openId, converted=True, createdDate=todayDate).count() > 0
  623. @classmethod
  624. def count_today_by_adId(cls, adId):
  625. # type:(str) -> int
  626. """
  627. 一些广告会有每日效果完成需要数额的限制
  628. TODO 也许需要提供可以修改的campaignTime
  629. :param adId:
  630. :return:
  631. """
  632. ad = Advertisement.get_by_adId(int(adId)) # type: Advertisement
  633. now = datetime.datetime.now()
  634. ad_campaign_started_datetime = ad.dateTimeAdded
  635. def _replace(dt):
  636. return dt.replace(
  637. hour=ad_campaign_started_datetime.hour,
  638. minute=ad_campaign_started_datetime.minute,
  639. second=ad_campaign_started_datetime.second)
  640. startTime = _replace(now)
  641. return cls.objects(adId=adId, dateTimeAdded__gte=startTime).count()
  642. def to_dict(self):
  643. sexMap = {0: '-', 1: u'男', 2: u'女', '': '-'}
  644. return {
  645. 'adId': self.adId,
  646. 'adName': self.adName,
  647. 'agentPrice': self.agentPrice,
  648. 'dealerPrice': self.dealerPrice,
  649. 'price': self.price,
  650. 'agentName': self.agentName,
  651. 'dealerName': self.dealerName,
  652. 'logicalCode': self.logicalCode,
  653. 'nickname': self.nickname,
  654. 'sex': sexMap.get(self.sex, '-'),
  655. 'openId': self.openId,
  656. 'timestamp': self.to_js_timestamp(self.dateTimeAdded),
  657. 'devType': self.devType,
  658. 'address': self.address,
  659. 'groupName': self.groupName
  660. }
  661. def to_dict_in_cn(self):
  662. sexMap = {0: '-', 1: u'男', 2: u'女'}
  663. return OrderedDict([
  664. (u'广告编号', self.adId),
  665. (u'广告名称', self.adName),
  666. (u'代理商价格', self.aIncome),
  667. (u'经销商价格', self.dIncome),
  668. (u'广告总价', self.mIncome),
  669. (u'点击时间', self.dateTimeAdded.strftime('%Y-%m-%d %H:%M:%S')),
  670. (u'代理商', self.agentName),
  671. (u'经销商', self.dealerName),
  672. (u'设备编码', self.logicalCode),
  673. (u'粉丝昵称', self.nickname),
  674. (u'粉丝性别', sexMap.get(self.sex, '-')),
  675. (u'粉丝ID', self.openId),
  676. (u'粉丝转化时间', self.dateTimeAdded.strftime('%Y-%m-%d %H:%M:%S')),
  677. (u'设备类型', self.devType),
  678. (u'设备地址', self.address),
  679. (u'设备地址名', self.groupName)
  680. ])
  681. def to_detail(self):
  682. # type: ()->dict
  683. """
  684. :return:
  685. """
  686. return self.to_dict()
  687. class AdPV(Searchable):
  688. dateTimeAdded = DateTimeField(default=datetime.datetime.now, verbose_name='添加进来的时间')
  689. createdDate = StringField(verbose_name='添加日期',
  690. default=lambda: datetime.datetime.now().strftime(Const.DATE_FMT))
  691. extra = DictField()
  692. meta = {
  693. 'collection': 'ad_pv',
  694. 'db_alias': 'logdata'
  695. }
  696. class AdWord(DynamicDocument):
  697. title = StringField()
  698. promotionUrl = StringField()
  699. dateTimeAdded = DateTimeField(default=datetime.datetime.now, verbose_name=u'添加进来的时间')
  700. dateTimeUpdated = DateTimeField(default=datetime.datetime.now, verbose_name=u'添加进来的时间')
  701. expireAt = DateTimeField(default=None, verbose_name=u'过期时间')
  702. dayQuota = IntField(default=sys.maxint, verbose_name=u'日限额')
  703. # totalQuota = IntField(default = sys.maxint, verbose_name = u'总共限额')
  704. used = DictField(default={}, verbose_name=u'使用计数')
  705. account = StringField(default=None, verbose_name=u'第三方推广平台账号')
  706. itemId = StringField(verbose_name=u'淘宝商品id', default=None)
  707. autoPass = BooleanField(verbose_name=u'是否需要生成口令', default=True)
  708. meta = {
  709. 'collection': 'ad_words',
  710. 'db_alias': 'logdata',
  711. 'ordering': ['-dateTimeAdded']
  712. }
  713. def to_dict(self):
  714. return {
  715. 'id': self.id,
  716. 'title': self.title,
  717. 'promotionUrl': self.promotionUrl,
  718. 'createdTime': self.dateTimeAdded
  719. }
  720. @classmethod
  721. def get_random_one(cls, **kwargs):
  722. # type: (**Any)->Optional[AdWord]
  723. today = today_format_str()
  724. kwargs.update({
  725. 'expireAt__gte': datetime.datetime.now(),
  726. '__raw__': {
  727. '$expr': {
  728. '$lt': ['$used.{}'.format(today), '$dayQuota']
  729. }
  730. }
  731. })
  732. return cls.objects(**kwargs).order_by('used.{}'.format(today)).first()
  733. def incr_count(self, count):
  734. today = today_format_str()
  735. yesterday = yesterday_format_str()
  736. update = {
  737. 'inc__used__total': count,
  738. 'inc__used__{}'.format(today): count,
  739. 'unset__used__{}'.format(yesterday): 0,
  740. 'dateTimeUpdated': datetime.datetime.now()
  741. }
  742. self.update(**update)
  743. class AdReportDay(Searchable):
  744. adId = IntField(verbose_name="广告ID")
  745. date = DateTimeField(verbose='date 统计运行的时间', default=datetime.datetime.now())
  746. reportDate = StringField(verbose='date 数据对应的时间', default='')
  747. requestCount = IntField(verbose_name=u'请求数目', default=0)
  748. meta = {'collection': 'ad_report_day', 'db_alias': 'logdata'}
  749. class DevRegToAli(Searchable):
  750. logicalCode = StringField(verbose_name=u'设备编号')
  751. responseData = StringField(verbose='注册回复的数据', default='')
  752. meta = {
  753. "collection": "dev_reg_aliyun",
  754. 'db_alias': 'logdata',
  755. "index": [
  756. "logicalCode"
  757. ]
  758. }
  759. sceneDict = {
  760. 'school': (u'学校'),
  761. }
  762. @staticmethod
  763. def get_school_scene(desc):
  764. if u'中' in desc or u'学校' in desc or u'卫校' in desc:
  765. return u'高中'
  766. if u'大学' in desc or u'院' in desc:
  767. return u'普通高等学校'
  768. else:
  769. return u'普通高等学校'
  770. @staticmethod
  771. def reg_to_aliyun_v3(dev):
  772. logger.debug('reg ali ad: {}'.format(dev.logicalCode))
  773. group = dev.group
  774. grpType = group['addressType']
  775. if grpType == 'school':
  776. first = u'学校'
  777. second = DevRegToAli.get_school_scene(group['groupName'] + group['address'])
  778. elif grpType == 'apartment':
  779. first = u'社区'
  780. second = u'普通社区'
  781. elif grpType == 'workshop':
  782. first = u'办公场所/园区'
  783. second = u'工业园区'
  784. elif grpType == 'mall':
  785. first = u'百货百货//购物中心购物中心'
  786. second = u'商业街'
  787. elif grpType == 'hospital':
  788. first = u'医院/医疗机构'
  789. second = u'综合医院'
  790. elif grpType == 'cafe':
  791. first = u'休闲娱乐'
  792. second = u'KTV/酒吧'
  793. elif grpType == 'KTV':
  794. first = u'休闲娱乐'
  795. second = u'KTV/酒吧'
  796. elif grpType == 'hotel':
  797. first = u'酒店'
  798. second = u'经济连锁'
  799. elif grpType == 'bar':
  800. first = u'休闲娱乐'
  801. second = u'KTV/酒吧'
  802. elif grpType == 'parking_lot':
  803. first = u'交通出行'
  804. second = u'停车场'
  805. elif grpType == 'square':
  806. first = u'公共区域/设施'
  807. second = u'城市公园/景区'
  808. elif grpType == 'bath_center':
  809. first = u'休闲娱乐'
  810. second = u'按摩/足疗/洗浴/汗蒸'
  811. else:
  812. first = u'社区'
  813. second = u'普通社区'
  814. try:
  815. areas = District.get_district(group['districtId']).split(' ')
  816. province, city, zone = areas[0], areas[1], areas[2]
  817. except Exception as e:
  818. # TODO: 没有先随便返回一个
  819. logger.exception(e)
  820. province, city, zone = u'山东省', u'滨州市', u"滨城区"
  821. dic = {
  822. 'first_scene': first,
  823. 'second_scene': second,
  824. 'outer_code': dev.logicalCode,
  825. 'province': province,
  826. 'city': city,
  827. 'district': zone,
  828. 'location_name': group['groupName'],
  829. 'floor': '1',
  830. 'device_model_number': dev.devTypeName or dev.majorDeviceType,
  831. }
  832. try:
  833. result = AlipayYunMaV3().reg_dev(dic)
  834. logger.debug(result.to_map())
  835. if result.body.code in ['0', '13002']:
  836. try:
  837. DevRegToAli.objects(logicalCode = dev.logicalCode).upsert_one(
  838. logicalCode = dev.logicalCode, responseData = json.dumps(result.to_map(), ensure_ascii = False)
  839. )
  840. except:
  841. pass
  842. return True
  843. else:
  844. return False
  845. except:
  846. return False
  847. class AdStatistics(DynamicDocument):
  848. adSpace = StringField(verbose_name=u"广告位")
  849. showType = StringField(verbose_name=u"广告类型")
  850. gateway = StringField(verbose_name=u'展示平台')
  851. makeCount = IntField(verbose_name=u"url生成次数", default=0)
  852. sDate = StringField(verbose_name=u'统计日期')
  853. meta = {
  854. "collection": "ad_statistics",
  855. "db_alias": "logdata",
  856. }
  857. @classmethod
  858. def inc_make_count(cls, adSpace, showType, gateway):
  859. cls.objects(sDate=datetime.datetime.now().strftime("%Y-%m-%d"), adSpace=adSpace, showType=showType,
  860. gateway=gateway).update_one(
  861. upsert=True, inc__makeCount=1)