models.py 36 KB

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