class Targeting(VkontakteAdsMixin, VkontakteModel): remote_pk_local_field = 'ad' ad = models.OneToOneField(Ad, verbose_name=u'Объявление', primary_key=True, related_name='targeting') campaign = models.ForeignKey(Campaign, verbose_name=u'Кампания') sex = models.PositiveSmallIntegerField(u'Пол', choices=TARGETING_SEX_CHOICES, default=0) age_from = models.PositiveSmallIntegerField(u'Возраст с', default=0) age_to = models.PositiveSmallIntegerField(u'Возраст до', default=0) birthday = models.CommaSeparatedIntegerField(u'День рождения', max_length=100, choices=[( '', u'Неважно'), (1, u'Сегодня'), (2, u'Завтра'), (3, u'Сегодня или завтра')], blank=True) country = models.PositiveIntegerField(u'Страна', default=0) cities = models.CommaSeparatedIntegerField(u'Города', max_length=500, blank=True) cities_not = models.CommaSeparatedIntegerField(u'Города исключить', max_length=500, blank=True) statuses = models.CommaSeparatedIntegerField(u'Семейное положение', max_length=500, blank=True) group_types = models.CommaSeparatedIntegerField(u'Категории групп', max_length=500, blank=True) groups = models.CommaSeparatedIntegerField(u'Группы', max_length=500, blank=True) religions = models.CommaSeparatedIntegerField(u'Религиозные взгляды', max_length=500, blank=True) interests = fields.CommaSeparatedCharField( u'Интересы', max_length=500, blank=True, help_text=u'Последовательность слов, разделенных запятой.') travellers = models.BooleanField(u'Путешественники', default=False) # Расширенная география districts = models.CommaSeparatedIntegerField(u'Районы', max_length=500, blank=True) stations = models.CommaSeparatedIntegerField(u'Станции метро', max_length=500, blank=True) streets = models.CommaSeparatedIntegerField(u'Улицы', max_length=500, blank=True) # Образование и работа schools = models.CommaSeparatedIntegerField(u'Учебные заведения', max_length=500, blank=True) positions = models.CommaSeparatedIntegerField(u'Должности', max_length=500, blank=True) school_from = models.PositiveSmallIntegerField(u'Окончание школы после', default=0) school_to = models.PositiveSmallIntegerField(u'Окончание школы дое', default=0) uni_from = models.PositiveSmallIntegerField(u'Окончание ВУЗа после', default=0) uni_to = models.PositiveSmallIntegerField(u'Окончание ВУЗа до', default=0) # Дополнительные параметры browsers = models.CommaSeparatedIntegerField(u'Браузеры и устройства', max_length=500, blank=True) tags = fields.CommaSeparatedCharField( u'Ключевые слова', max_length=200, blank=True, help_text=u'Набор строк, разделенных запятой.') # not exist in API docs approved = models.BooleanField(u'Одобрено', default=False) count = models.PositiveIntegerField(null=True, blank=True, help_text=u'') operators = models.CommaSeparatedIntegerField(u'Операторы', max_length=500, blank=True, help_text=u'') remote = VkontakteManager( remote_pk=('ad_id',), methods = {'get': 'getAdsTargeting'} ) class Meta: verbose_name = u'Таргетинг объявления Вконтакте' verbose_name_plural = u'Таргетинг объявления Вконтакте'
class Image(VkontakteAdsMixin, VkontakteModel): ''' Model of vkontakte image ''' def _get_upload_to(self, filename=None): return 'images/%f.jpg' % time.time() # ad = models.OneToOneField(Ad, related_name='image') ad = models.OneToOneField(Ad, verbose_name=u'Объявление', primary_key=True, related_name='image') hash = models.CharField( max_length=50, blank=True, help_text=u'Значение, полученное в результате загрузки фотографии на сервер') photo_hash = models.CharField( max_length=50, blank=True, help_text=u'Значение, полученное в результате загрузки фотографии на сервер') photo = models.CharField( max_length=200, blank=True, help_text=u'Значение, полученное в результате загрузки фотографии на сервер') server = models.PositiveIntegerField( blank=True, null=True, help_text=u'Значение, полученное в результате загрузки фотографии на сервер') size = models.CharField(max_length=1, blank=True) aid = models.PositiveIntegerField(null=True, blank=True) width = models.PositiveIntegerField(null=True, blank=True) height = models.PositiveIntegerField(null=True, blank=True) file = models.ImageField(u'Картинка', upload_to=_get_upload_to, blank=True) # not in API post_url = models.CharField(max_length=200, blank=True, help_text=u'Адрес загрузки картинки на сервер') remote = VkontakteManager(methods={'get_post_url': 'getUploadURL'}) class Meta: verbose_name = u'Картинка объявления Вконтакте' verbose_name_plural = u'Картинка объявления Вконтакте' def get_post_url(self): self.post_url = Image.remote.api_call(method='get_post_url', cost_type=self.ad.cost_type) return self.post_url def upload(self): if self.file: # save file to disk if not self.file._committed: self.file.field.pre_save(self, True) url = self.post_url or self.get_post_url() files = { 'file': (self.file.name.split('/')[-1], open(os.path.join(settings.MEDIA_ROOT, self.file.name), 'rb'))} response = requests.post(url, files=files) response = json.loads(response.content) if 'errcode' in response: raise VkontakteContentError("Error with code %d while uploading image %s" % (response['errcode'], self.file)) self.parse(response)
class Budget(VkontakteAdsModel): account = models.ForeignKey( Account, primary_key=True, help_text=u'Номер рекламного кабинета, бюджет которого запрашивается.') budget = models.DecimalField( max_digits=10, decimal_places=2, help_text=u'Оставшийся бюджет в указанном рекламном кабинете.') remote = VkontakteManager(remote_pk=('account',), methods={'get': 'getBudget'}) class Meta: verbose_name = u'Бюджет личного кабинета Вконтакте' verbose_name_plural = u'Бюджеты личных кабинетов Вконтакте'
class Client(VkontakteAdsIDContentModel): fields_required_for_update = ['client_id'] account = models.ForeignKey(Account, verbose_name=u'Аккаунт', related_name='clients', help_text=u'Номер рекламного кабинета, в котором должны создаваться кампании.') name = models.CharField(u'Название', max_length=60) day_limit = models.IntegerField(u'Дневной лимит', null=True, help_text=u'Целое число рублей.') all_limit = models.IntegerField(u'Общий лимит', null=True, help_text=u'Целое число рублей.') statistics = generic.GenericRelation('Statistic', verbose_name=u'Статистика') objects = VkontakteCRUDManager() remote = VkontakteManager( remote_pk=('remote_id',), methods = { 'get': 'getClients', 'create': 'createClients', 'update': 'updateClients', 'delete': 'deleteClients', }) class Meta: verbose_name = u'Рекламный клиент Вконтакте' verbose_name_plural = u'Рекламные клиенты Вконтакте' def __str__(self): return self.name def fields_for_update(self): fields = self.fields_for_create() fields.update(client_id=self.remote_id) return fields def fields_for_create(self): fields = dict(name=self.name) if self.day_limit: fields.update(day_limit=self.day_limit) if self.all_limit: fields.update(all_limit=self.all_limit) return fields def fetch_campaigns(self, ids=None): ''' Get all campaigns of account ''' return super(Client, self).fetch_campaigns(account=self.account, client=self, ids=ids)
class Account(VkontakteAdsIDModel): remote_pk_field = 'account_id' name = models.CharField(u'Название', blank=True, max_length=100) account_status = models.BooleanField(default=False, help_text=u'Cтатус рекламного кабинета. активен / неактивен.') access_role = models.CharField( choices=ACCOUNT_ACCESS_ROLE_CHOICES, max_length=10, help_text=u'права пользователя в рекламном кабинете.') remote = VkontakteManager(remote_pk=('remote_id',), methods={ 'get': 'getAccounts' }) statistics = generic.GenericRelation('Statistic', verbose_name=u'Статистика') class Meta: verbose_name = u'Рекламный кабинет Вконтакте' verbose_name_plural = u'Рекламные кабинеты Вконтакте' def __str__(self): return self.name or 'Account #%s' % self.remote_id def _substitute(self, old_instance): super(Account, self)._substitute(old_instance) self.name = old_instance.name def fetch_clients(self): ''' Get all clients of account ''' try: instances = Client.remote.get(account_id=self.remote_id) except VKError, e: if e.code == 100: return [] else: raise e instances_saved = [] for instance in instances: instance.account = self instance.fetched = datetime.now() instance._commit_remote = False instances_saved += [Client.remote.get_or_create_from_instance(instance)] return instances_saved
class Layout(VkontakteAdsMixin, VkontakteModel): remote_pk_local_field = 'ad' ad = models.OneToOneField(Ad, verbose_name=u'Объявление', primary_key=True, related_name='layout') campaign = models.ForeignKey(Campaign, verbose_name=u'Кампания', help_text=u'Кампания объявления.') # change max_length=50, because found string with len=27 title = fields.CharRangeLengthField( u'Заголовок', min_length=3, max_length=50, help_text=u'Заголовок объявления - строка длиной от 3 до 25 символов') # change max_length=100, because found string with len=65 description = fields.CharRangeLengthField( u'Описание', min_length=3, max_length=100, help_text=u'Описание объявления - строка длиной от 3 до 60 символов - обязательно при выборе типа "оплата за переходы"') link_url = models.URLField( u'Ссылка', max_length=500, help_text=u'Ссылка на рекламируемый объект в формате http://yoursite.com или ВКонтакте API. Если в ссылке содержатся строки "{ad_id}" или "{campaign_id}", то они заменяются соответственно на ID объявления и ID кампании в момент перехода пользователя по такой ссылке.') link_domain = models.CharField( u'Домен', blank=True, max_length=50, help_text=u'Домен рекламируемого объекта в формате yoursite.com') # not exist in API docs preview_link = models.CharField(u'Превью', blank=True, max_length=200) # preview content preview = models.TextField() remote = VkontakteManager( remote_pk=('ad_id',), methods = {'get': 'getAdsLayout'} ) class Meta: verbose_name = u'Контент объявления Вконтакте' verbose_name_plural = u'Контент объявления Вконтакте' def save(self, *args, **kwargs): self.set_preview() super(Layout, self).save(*args, **kwargs) def set_preview(self): if self.preview_link: response = requests.get(self.preview_link) # decode, becouse otherwise test would crash self.preview = response.content.decode('windows-1251', 'ignore')
class AdAbstract(VkontakteAdsIDContentModel): ''' Abstract model of vkontakte ads with all fields for some special needs ''' account = models.ForeignKey(Account, verbose_name=u'Аккаунт', related_name='ads', help_text=u'Номер рекламного кабинета, в котором создается объявление.') campaign = ChainedForeignKey(Campaign, verbose_name=u'Кампания', chained_field="account", chained_model_field="account", show_all=False, auto_choose=True, related_name='ads', help_text=u'Кампания, в которой будет создаваться объявление.') # max_lengh=100 потому что иногда рекламы созданные через интерфейс ВК имеют названия длиннее name = fields.CharRangeLengthField(u'Название', min_length=3, max_length=100, help_text=u'Название объявления (для использования в рекламном кабинете) - строка длиной от 3 до 60 символов.') all_limit = models.PositiveIntegerField(u'Общий лимит', null=True, help_text=u'Целое число рублей.') cost_type = models.PositiveSmallIntegerField(u'Тип оплаты', choices=( (0, u'оплата за переходы'), (1, u'оплата за показы')), help_text=u'Флаг, описывающий тип оплаты') cpc = fields.IntegerRangeField(u'Цена за переход', min_value=50, null=True, blank=True, help_text=u'Если оплата за переходы, цена за переход в копейках, минимум 50 коп.') cpm = models.PositiveIntegerField( u'Цена за показы', null=True, blank=True, help_text=u'Если оплата за показы, цена за 1000 показов в копейках') status = models.BooleanField(u'Статус', default=False, help_text=u'Статус рекламного объявления: остановлено / запущено.') disclaimer = models.BooleanField( u'Противопоказания', default=False, help_text=u'Укажите, если имеются противопоказания (только для рекламы медицинских товаров и услуг).') # not exist in API docs approved = models.BooleanField(u'Одобрено', default=False) statistics = generic.GenericRelation('Statistic', verbose_name=u'Статистика') objects = VkontakteCRUDManager() remote = VkontakteManager( remote_pk=('remote_id',), methods = { 'get': 'getAds', 'create': 'createAds', 'update': 'updateAds', 'delete': 'deleteAds', }) class Meta: abstract = True
class Campaign(VkontakteAdsIDContentModel): account = models.ForeignKey(Account, verbose_name=u'Аккаунт', related_name='campaigns', help_text=u'Номер рекламного кабинета, в котором должны создаваться кампании.') client = ChainedForeignKey(Client, verbose_name=u'Клиент', chained_field="account", chained_model_field="account", show_all=False, auto_choose=True, related_name='campaigns', null=True, blank=True, help_text=u'Только для рекламных агентств. id клиента, в рекламном кабинете которого будет создаваться кампания.') name = fields.CharRangeLengthField( u'Название', min_length=3, max_length=60, help_text=u'Название рекламной кампании - строка длиной от 3 до 60 символов.') day_limit = models.IntegerField(u'Дневной лимит', null=True, help_text=u'Целое число рублей.') all_limit = models.IntegerField(u'Общий лимит', null=True, help_text=u'Целое число рублей.') start_time = models.DateTimeField( u'Время запуска', null=True, blank=True, help_text=u'Время запуска кампании в unixtime формате.') stop_time = models.DateTimeField( u'Время остановки', null=True, blank=True, help_text=u'Время остановки кампании в unixtime формате.') status = models.BooleanField(u'Статус', default=False, help_text=u'Статус рекламной кампании: остановлена / запущена.') statistics = generic.GenericRelation('Statistic', verbose_name=u'Статистика') objects = VkontakteCRUDManager() remote = VkontakteManager( remote_pk=('remote_id',), methods = { 'get': 'getCampaigns', 'create': 'createCampaigns', 'update': 'updateCampaigns', 'delete': 'deleteCampaigns', }) class Meta: verbose_name = u'Рекламная кампания Вконтакте' verbose_name_plural = u'Рекламные кампании Вконтакте' def _substitute(self, old_instance): super(Campaign, self)._substitute(old_instance) self.account = old_instance.account self.client = old_instance.client @property def refresh_kwargs(self): kwargs = super(Campaign, self).retresh_kwargs kwargs['account_id'] = self.account.remote_id kwargs['campaign_ids'] = [self.remote_id] if self.client: kwargs['client_id'] = self.client.remote_id return kwargs def check_remote_existance(self, *args, **kwargs): existance = super(Campaign, self).check_remote_existance(**kwargs) if not existance: for ad in self.ads.all(): ad.archived = True ad.save(commit_remote=False) return existance fields_required_for_update = ['campaign_id'] def fields_for_update(self): ''' TODO: add dropping start_time, stop_time http://vk.com/developers.php?oid=-1&p=ads.updateCampaigns ''' fields = self.fields_for_create() if 'client_id' in fields: fields.pop('client_id') fields.update(campaign_id=self.remote_id) return fields def fields_for_create(self): fields = dict(name=self.name, status=int(self.status)) if self.client: fields.update(client_id=self.client.remote_id) if self.day_limit: fields.update(day_limit=self.day_limit) if self.all_limit: fields.update(all_limit=self.all_limit) if self.start_time: fields.update(start_time=int(time.mktime(self.start_time.timetuple()))) if self.stop_time: fields.update(stop_time=int(time.mktime(self.stop_time.timetuple()))) return fields def parse(self, response): # Convert status=2 to flag archived if response['status'] == 2: response['status'] = 0 self.archived = True super(Campaign, self).parse(response) def fetch_ads(self, ids=None): ''' Get all ads of campaign ''' return super(Campaign, self).fetch_ads(model=Ad, ids=ids) def fetch_ads_targeting(self, ids=None): ''' Get all ad targetings of campaign ''' return super(Campaign, self).fetch_ads(model=Targeting, ids=ids) def fetch_ads_layout(self, ids=None): ''' Get all ad layouts of campaign ''' return super(Campaign, self).fetch_ads(model=Layout, ids=ids)