# -*- coding: utf-8 -*- """Models for emencia.django.newsletter""" from smtplib import SMTP from datetime import datetime from datetime import date from datetime import timedelta from dateutil import relativedelta from email.MIMEImage import MIMEImage from random import choice from itertools import chain, groupby import copy import operator from pymorphy2 import MorphAnalyzer from django.db import models from django.db.models import Q from django.utils.encoding import smart_str from django.utils.encoding import force_unicode from django.utils.translation import ugettext_lazy as _ from django.utils.translation import activate from django.utils.safestring import mark_safe from django.conf import settings from django.core.mail import EmailMessage from django.core.validators import MaxValueValidator, MinValueValidator from django.core.urlresolvers import reverse from django.contrib.sites.models import Site from django.contrib.contenttypes import generic from django.contrib.contenttypes.models import ContentType from django.contrib.auth.models import Group from django.template.loader import render_to_string, get_template from django.template import Context from django.http import QueryDict from emencia.django.newsletter.managers import ContactManager from emencia.django.newsletter.settings import BASE_PATH from emencia.django.newsletter.settings import MAILER_HARD_LIMIT from emencia.django.newsletter.settings import DEFAULT_HEADER_REPLY from emencia.django.newsletter.settings import DEFAULT_HEADER_SENDER from emencia.django.newsletter.settings import ACTIVATION_SUBJECT, ACTIVATION_TEMPLATE, DEFAULT_SMPTSERVER_ID from emencia.django.newsletter.utils.vcard import vcard_contact_export from emencia.django.newsletter.utils import make_activation_code from functions.form_check import translit_with_separator from functions.model_utils import EnumChoices from functions.files import gen_path from exposition.models import Exposition from conference.models import Conference from article.models import Article from haystack.query import SearchQuerySet, RelatedSearchQuerySet, SQ from haystack.inputs import Exact morph = MorphAnalyzer() class SMTPServer(models.Model): """Configuration of a SMTP server""" name = models.CharField(_('name'), max_length=255) host = models.CharField(_('server host'), max_length=255) user = models.CharField(_('server user'), max_length=128, blank=True, help_text=_('Leave it empty if the host is public.')) password = models.CharField(_('server password'), max_length=128, blank=True, help_text=_('Leave it empty if the host is public.')) port = models.IntegerField(_('server port'), default=25) tls = models.BooleanField(_('server use TLS')) headers = models.TextField(_('custom headers'), blank=True, help_text=_('key1: value1 key2: value2, splitted by return line.\n'\ 'Useful for passing some tracking headers if your provider allows it.')) mails_hour = models.IntegerField(_('mails per hour'), default=0) def connect(self): """Connect the SMTP Server""" smtp = SMTP(smart_str(self.host), int(self.port)) smtp.ehlo_or_helo_if_needed() if self.tls: smtp.starttls() smtp.ehlo_or_helo_if_needed() if self.user or self.password: smtp.login(smart_str(self.user), smart_str(self.password)) return smtp def delay(self): """compute the delay (in seconds) between mails to ensure mails per hour limit is not reached :rtype: float """ if not self.mails_hour: return 0.0 else: return 3600.0 / self.mails_hour def credits(self): """Return how many mails the server can send""" if not self.mails_hour: return MAILER_HARD_LIMIT last_hour = datetime.now() - timedelta(hours=1) sent_last_hour = ContactMailingStatus.objects.filter( models.Q(status=ContactMailingStatus.SENT) | models.Q(status=ContactMailingStatus.SENT_TEST), newsletter__server=self, creation_date__gte=last_hour).count() return self.mails_hour - sent_last_hour @property def custom_headers(self): if self.headers: headers = {} for header in self.headers.splitlines(): if header: key, value = header.split(':') headers[key.strip()] = value.strip() return headers return {} def __unicode__(self): return '%s (%s)' % (self.name, self.host) class Meta: verbose_name = _('SMTP server') verbose_name_plural = _('SMTP servers') class Contact(models.Model): """Contact for emailing""" PERIODIC_CHOICES = EnumChoices( WEEK = (1, _(u'Раз в неделю')), WEEK_2 = (2, _(u'Раз в 2 недели')), MONTH = (3, _(u'Раз в месяц')), ) PERIODIC_DAY_CHOICES = EnumChoices( MON=(1, _(u'Пн')), TUE=(2, _(u'Вт')), WED=(3, _(u'Ср')), THU=(4, _(u'Чт')), FRI=(5, _(u'Пт')), # Sat=(6, _(u'Суббота')), # Sun=(7, _(u'Воскресенье')), ) activation_subject = ACTIVATION_SUBJECT activation_template = ACTIVATION_TEMPLATE user = models.ForeignKey('accounts.User', null=True) email = models.EmailField(_('email'), unique=True, max_length=255) first_name = models.CharField(_('first name'), max_length=50, blank=True) last_name = models.CharField(_('last name'), max_length=50, blank=True) subscriber = models.BooleanField(_('subscriber'), default=True) valid = models.BooleanField(_('valid email'), default=True) tester = models.BooleanField(_('contact tester'), default=False) activation_code = models.CharField( verbose_name=_('activation code'), max_length=40, default=make_activation_code ) activated = models.BooleanField(default=False) from_users = models.BooleanField(default=False) dailymailing = models.BooleanField(default=False) moscow = models.BooleanField(_(u'Москва'), blank=True, default=False) russia = models.BooleanField(_(u'Россия'), blank=True, default=False) r_cities = models.ManyToManyField('city.City', blank=True, null=True, verbose_name=_(u'Города России')) foreign = models.BooleanField(_(u'Зарубеж'), blank=True, default=False) f_countries = models.ManyToManyField('country.Country', blank=True, null=True, verbose_name=_(u'Зарубежные страны')) area = models.ManyToManyField('country.Area', blank=True, null=True, verbose_name=_(u'Географическая зона')) periodic = models.PositiveSmallIntegerField(_(u'Периодичность отправки'), choices=PERIODIC_CHOICES, default=PERIODIC_CHOICES.WEEK) periodic_day = models.PositiveSmallIntegerField(_(u'День отправки'), choices=PERIODIC_DAY_CHOICES, default=PERIODIC_DAY_CHOICES.WED) content_news = models.BooleanField(_(u'Новости событий'), blank=True, default=True) content_overview = models.BooleanField(_(u'Статьи для экспонентов'), blank=True, default=True) content_articles = models.BooleanField(_(u'Статьи для организаторов событий'), blank=True, default=True) tags = models.ManyToManyField('theme.Tag', verbose_name=_(u'Теги'), blank=True, null=True) themes = models.ManyToManyField('theme.Theme', blank=True, null=True, verbose_name=_(u'Тематики')) conferences = models.ManyToManyField('conference.Conference', verbose_name=_(u'Конференции'), null=True) expositions = models.ManyToManyField('exposition.Exposition', verbose_name=_(u'Выставки'), null=True) content_type = models.ForeignKey(ContentType, blank=True, null=True) object_id = models.PositiveIntegerField(blank=True, null=True) content_object = generic.GenericForeignKey('content_type', 'object_id') creation_date = models.DateTimeField(_('creation date'), auto_now_add=True) modification_date = models.DateTimeField(_('modification date'), auto_now=True) last_mailing_date = models.DateField(_(u'Дата посленего автоматического письма'), blank=True, null=True) objects = ContactManager() def unsubscribe(self): self.subscriber = False self.moscow = False self.russia = False self.foreign = False self.content_news = False self.content_overview = False self.content_articles = False self.tags.clear() self.themes.clear() self.f_countries.clear() self.r_cities.clear() self.save() def subscriptions(self): """Return the user subscriptions""" return MailingList.objects.filter(subscribers=self) def unsubscriptions(self): """Return the user unsubscriptions""" return MailingList.objects.filter(unsubscribers=self) def vcard_format(self): return vcard_contact_export(self) def mail_format(self): if self.first_name and self.last_name: return '%s %s <%s>' % (self.last_name, self.first_name, self.email) return self.email mail_format.short_description = _('mail format') def get_absolute_url(self): if self.content_type and self.object_id: return self.content_object.get_absolute_url() return reverse('admin:newsletter_contact_change', args=(self.pk,)) def get_or_fillattr(self, attr, func): value = getattr(self, 'attr', None) if value is None: value = func() setattr(self, attr, value) return value def get_r_cities(self): return self.get_or_fillattr('_r_cities', lambda: list(self.r_cities.language().exclude(pk=settings.MOSCOW_PK))) def get_r_cities_pks(self): return [x.pk for x in self.get_r_cities()] def get_f_countries(self): return self.get_or_fillattr('_f_countries', lambda: list(self.f_countries.language().all())) def get_areas(self): return self.get_or_fillattr('_areas', lambda: list(self.area.language().all())) def get_areas_pks(self): return [x.pk for x in self.get_areas()] def build_dailymail_preheader(self, preheader_ctx=None): preheader_ctx = preheader_ctx or {} themes = preheader_ctx.get('themes') themes_string = '' if themes: themes_string = u'по теме ' + themes geo = [] geo_string = u'' if self.moscow: geo.append(u'Москве') if self.russia: if not self.get_r_cities(): geo.append(u'России') else: for city in self.get_r_cities()[:3]: try: geo.append(morph.parse(city.name)[0].inflect({'loct'}).word.capitalize()) except: pass if self.foreign: if not self.get_f_countries() and not self.get_areas(): geo.append(u'зарубежом') else: for x in list(chain(self.get_f_countries(), self.get_areas()))[:3]: geo.append(morph.parse(x.name)[0].inflect({'loct'}).word.capitalize()) if geo: geo_string = u'в ' + u', '.join(geo[:3]) if len(geo) > 3: geo_string += u' и др. местам' return u' '.join(filter(lambda x: x, [u'Все главные события', themes_string, geo_string])) def get_announce_context_v2(self, date=None): # import error from emencia.django.newsletter.utils.tokens import tokenize date = date or datetime.today().date() activate('ru') ctx = { 'recommended': [], 'moscow': [], 'russia': [], 'foreign': [], 'news': [], 'blog': [], 'mailing_settings_url': reverse('newsletter-authmailingsettings', args=tokenize(self)), } th_tg_filter_params = [] th_tg_structure = {} th_tg_filter = None for tag, theme in self.tags.values_list('pk', 'theme_id'): th_tg_structure.setdefault(theme, []).append(tag) for theme in self.themes.values_list('pk', flat=True): th_tg_structure.setdefault(theme, []) if th_tg_structure: for theme, tags in th_tg_structure.iteritems(): p = {'theme': theme} if tags: p.update({'tag__in': tags}) th_tg_filter_params.append(SQ(**p)) th_tg_filter = reduce(operator.or_, th_tg_filter_params) # recommended paid_recommend = NewsletterRecommend.objects.filter( fr__lte=date, to__gte=date) recommended = SearchQuerySet().models(Exposition, Conference)\ .filter(expohit=True, data_begin__gte=date) if th_tg_filter: paid_recommend = list(paid_recommend.filter(Q(theme__in=th_tg_structure.keys()) | Q(theme__isnull=True))) recommended = recommended.filter(th_tg_filter) try: ctx['recommended'] = choice(paid_recommend).event() except: try: ctx['recommended'] = recommended[0].object except: pass # moscow if self.moscow: moscow_sqs = SearchQuerySet().models(Exposition, Conference)\ .filter(city_id=Exact(settings.MOSCOW_PK), data_begin__gte=date) if th_tg_filter is not None: moscow_sqs = moscow_sqs.filter(th_tg_filter) ctx['moscow'] = moscow_sqs.order_by('data_begin')[:4] moscow_filter_url = QueryDict('', mutable=True) moscow_filter_url.setlist('theme', th_tg_structure.keys()) moscow_filter_url.setlist('tag', list(chain(*th_tg_structure.values()))) moscow_filter_url['city'] = settings.MOSCOW_PK moscow_filter_url['date_from'] = date.strftime('%d.%m.%Y') ctx['moscow_filter_url'] = mark_safe(moscow_filter_url.urlencode()) # russia if self.russia: r_cities = set(self.get_r_cities_pks()) r_date = date + relativedelta.relativedelta(months=1) russia_sqs = SearchQuerySet().models(Exposition, Conference) \ .filter(country_id=Exact(settings.RUSSIA_PK), data_begin__gte=r_date) \ # .exclude(city_id=Exact(settings.MOSCOW_PK)) # if r_cities or self.moscow: # russia_sqs = russia_sqs.exclude(city_id=Exact(settings.MOSCOW_PK)) if r_cities: russia_sqs = russia_sqs.filter(city_id__in=r_cities) if th_tg_filter is not None: russia_sqs = russia_sqs.filter(th_tg_filter) ctx['russia'] = russia_sqs.order_by('data_begin')[:3] russia_filter_url = QueryDict('', mutable=True) russia_filter_url.setlist('theme', th_tg_structure.keys()) russia_filter_url.setlist('tag', list(chain(*th_tg_structure.values()))) russia_filter_url['country'] = settings.RUSSIA_PK russia_filter_url['date_from'] = r_date.strftime('%d.%m.%Y') ctx['russia_filter_url'] = mark_safe(russia_filter_url.urlencode()) # foreign if self.foreign: f_countries_cleaned = list(self.f_countries.order_by('pk').values_list('pk', 'area_id')) areas = set(self.get_areas_pks()) areas_with_country = set() f_countries = set() for area, group in groupby(f_countries_cleaned, lambda x: x[1]): areas_with_country.add(area) f_countries.update(map(lambda x: x[0], group)) full_areas = areas.difference(areas_with_country) params_list = [] if f_countries: params_list.append(SQ(country_id__in=f_countries)) if full_areas: params_list.append(SQ(area_id__in=full_areas)) foreign_sqs = SearchQuerySet().models(Exposition, Conference)\ .filter(data_begin__gte=date) if params_list: foreign_sqs = foreign_sqs.filter(reduce(operator.or_, params_list)) if th_tg_filter is not None: foreign_sqs = foreign_sqs.filter(th_tg_filter) ctx['foreign'] = foreign_sqs.order_by('data_begin')[:4] foreign_filter_url = QueryDict('', mutable=True) foreign_filter_url.setlist('theme', th_tg_structure.keys()) foreign_filter_url.setlist('tag', list(chain(*th_tg_structure.values()))) foreign_filter_url.setlist('country', f_countries) foreign_filter_url['date_from'] = date.strftime('%d.%m.%Y') ctx['foreign_filter_url'] = mark_safe(foreign_filter_url.urlencode()) # news if self.content_news: news = Article.objects.news().filter(draft=False) if self.last_mailing_date: news = news.filter(publish_date__gte=self.last_mailing_date) try: ctx['news'] = news.order_by('-publish_date')[0] except: pass # blog if self.content_articles: blog = Article.objects.blogs() if self.last_mailing_date: blog = blog.filter(publish_date__gte=self.last_mailing_date) try: ctx['blog'] = blog.order_by('-publish_date')[0] except: pass # import pdb; pdb.set_trace() return ctx def get_announce_context(self): activate('ru') expo = Exposition.enable.upcoming() conf = Conference.enable.upcoming() themes = list(self.contactsettings.theme.all()) # making query params = Q() _countries = set(self.contactsettings.country.all().values_list('pk', flat=True)) countries = set() # countries_overall = set() _cities = self.contactsettings.city.all().values_list('pk', 'country_id') cities = set() cities_overall = set() for city, country_id in _cities: if country_id in _countries: cities.add(city) countries.add(country_id) else: cities_overall.add(city) countries_overall = _countries.difference(countries) if countries_overall: params |= Q(country_id__in=countries_overall) if cities_overall: params |= Q(city_id__in=cities_overall) if countries and cities: params |= Q(city_id__in=cities, country_id__in=countries) if themes: theme_ids = set([item.id for item in themes]) params &= Q(theme__in=theme_ids) expo = list(expo.filter(params)[:5]) conf = list(conf.filter(params)[:5]) if expo or conf: context = { 'themes': themes, 'expo': expo, 'conf': conf, } if expo: context['expo_places'] = u', '.join(set([x.country.inflect for x in expo])) if conf: context['conf_places'] = u', '.join(set([x.country.inflect for x in conf])) else: # no events for announce return None news = list(Article.objects.news() .filter(publish_date__gt=datetime.now()-timedelta(days=7), theme__in=theme_ids) .distinct()[:3]) if news: context['news'] = news blogs = list(Article.objects.blogs() .filter(publish_date__gt=datetime.now()-timedelta(days=7)) .distinct()[:3]) if blogs: context['blogs'] = blogs return context def __unicode__(self): if self.first_name and self.last_name: contact_name = '%s %s' % (self.last_name, self.first_name) else: contact_name = self.email return contact_name def send_activation(self): user = self site = Site.objects.get_current() ctx_dict = {'activation_key': self.activation_code, 'site': site, 'user': user} subject = render_to_string(self.activation_subject, ctx_dict) # Email subject *must not* contain newlines subject = ''.join(subject.splitlines()) message = get_template(self.activation_template).render(Context(ctx_dict)) msg = EmailMessage(subject, message, settings.DEFAULT_FROM_EMAIL, [user.email]) msg.content_subtype = "html" images =(('/img/logo_reg.png', 'logo'), # ('/img/soc-medias/sm-icon-rss.png', 'rss'), ('/img/soc-medias/sm-icon-fb.png', 'fb'), ('/img/soc-medias/sm-icon-lin.png', 'linkedin'), ('/img/soc-medias/sm-icon-vk.png', 'vk'), ('/img/soc-medias/sm-icon-twit.png', 'twit')) for img in images: fp = open(settings.STATIC_ROOT+img[0], 'rb') msg_img = MIMEImage(fp.read()) fp.close() msg_img.add_header('Content-ID', '<'+img[1]+'>') msg.attach(msg_img) msg.send() class Meta: ordering = ('-modification_date',) verbose_name = _('contact') verbose_name_plural = _('contacts') class ContactSettings(models.Model): WEEK = 1 # every week WEEK_2 = 2 # every 2 weeks MONTH = 3 # every 4 weeks PERIODIC_CHOICES = [(WEEK, _(u'Раз в неделю')), (WEEK_2, _(u'Раз в 2 недели')), (MONTH, _(u'Раз в месяц')) ] contact = models.OneToOneField(Contact) periodic = models.PositiveIntegerField(verbose_name=_(u'Периодичность отправки'), choices=PERIODIC_CHOICES, default=WEEK) exponent_practicum = models.BooleanField(default=True, verbose_name=_(u'Практикум экспонента')) organiser_practicum = models.BooleanField(default=False, verbose_name=_(u'Практикум организатора событий')) theme = models.ManyToManyField('theme.Theme', blank=True, null=True, verbose_name=_(u'Тематики')) area = models.ManyToManyField('country.Area', blank=True, null=True, verbose_name=_(u'Географические зоны')) country = models.ManyToManyField('country.Country', blank=True, null=True, verbose_name=_(u'Страны')) city = models.ManyToManyField('city.City', blank=True, null=True, verbose_name=_(u'Города')) def save_mailing_lists(self): practicum_exponent = MailingList.objects.get(id=2) practicum_organiser = MailingList.objects.get(id=3) contact = self.contact if self.exponent_practicum: practicum_exponent.subscribers.add(contact) else: practicum_exponent.subscribers.remove(contact) if self.organiser_practicum: practicum_organiser.subscribers.add(contact) else: practicum_organiser.subscribers.remove(contact) def save(self, *args, **kwargs): super(ContactSettings, self).save(*args, **kwargs) try: self.save_mailing_lists() except: pass class MailingList(models.Model): """Mailing list""" name = models.CharField(_('name'), max_length=255) description = models.TextField(_('description'), blank=True) subscribers = models.ManyToManyField(Contact, verbose_name=_('subscribers'), related_name='mailinglist_subscriber') unsubscribers = models.ManyToManyField(Contact, verbose_name=_('unsubscribers'), related_name='mailinglist_unsubscriber', null=True, blank=True) announce = models.BooleanField(default=False) announce_public_day = models.IntegerField(default=2) creation_date = models.DateTimeField(_('creation date'), auto_now_add=True) modification_date = models.DateTimeField(_('modification date'), auto_now=True) theme_for_filter = models.ManyToManyField('theme.Theme', verbose_name=_(u'Тематики'), null=True, blank=True) def subscribers_count(self): return self.subscribers.all().count() subscribers_count.short_description = _('subscribers') def unsubscribers_count(self): return self.unsubscribers.all().count() unsubscribers_count.short_description = _('unsubscribers') def expedition_set(self): unsubscribers_id = self.unsubscribers.values_list('id', flat=True) if not self.announce: return self.subscribers.valid_subscribers().exclude( id__in=unsubscribers_id) else: return self.subscribers.valid_subscribers().exclude( id__in=unsubscribers_id).filter(contactsettings__theme__isnull=False).distinct() def __unicode__(self): return self.name def check_day(self, day): weekday = day.weekday() if self.announce_public_day > 0: return (self.announce_public_day - 1) == weekday else: return weekday == 6 def generate_announce(self, day): # simple data sending_date = day + timedelta(days=1) start_sending_time = datetime.combine(sending_date, datetime.min.time()) title = u'Анонс событий на %s' % sending_date.strftime('%Y-%m-%d') newsletter = Newsletter(status=Newsletter.WAITING, sending_date=start_sending_time, title=title, mailing_list=self, slug=translit_with_separator(title)) newsletter.save() # add default attacments copied from default announce newsletter default_newsletter = Newsletter.objects.get(id=1) attacments = list(default_newsletter.attachment_set.all()) for a in attacments: a.id = None a.newsletter = newsletter a.save() return newsletter def generate_announce_newsletter(self, day): if self.check_day(day): return self.generate_announce(day) return None class Meta: ordering = ('-creation_date',) verbose_name = _('mailing list') verbose_name_plural = _('mailing lists') class Newsletter(models.Model): """Newsletter to be sended to contacts""" DRAFT = 0 WAITING = 1 SENDING = 2 SENT = 4 CANCELED = 5 STATUS_CHOICES = ((DRAFT, _('draft')), (WAITING, _('waiting sending')), (SENDING, _('sending')), (SENT, _('sent')), (CANCELED, _('canceled')), ) # выбираем вариант письма для финальной рассылки (А-В тестирование) A = 1 B = 2 AB_FINAL_CHOICE = ( (A, _(u'Письмо типа А')), (B, _(u'Письмо типа B')), ) preheader = models.CharField(_(u'Прехедер'), max_length=255, null=True, blank=True) preheader2 = models.CharField(_(u'Прехедер B'), max_length=255, null=True, blank=True) title = models.CharField(_('title'), max_length=255, help_text=_('You can use the "{{ UNIQUE_KEY }}" variable ' \ 'for unique identifier within the newsletter\'s title.')) title2 = models.CharField(_('title B'), max_length=255, help_text=_('You can use the "{{ UNIQUE_KEY }}" variable ' \ 'for unique identifier within the newsletter\'s title.'), null=True, blank=True) content = models.TextField(_('content'), help_text=_('Or paste an URL.'), default='') content2 = models.TextField(_('content B'), help_text=_('Or paste an URL.'), default='') ab_testing = models.BooleanField(_('A-B testing'), default=False) ab_first_stage = models.PositiveSmallIntegerField(_(u'% в первый этап'), null=True, blank=True, validators=[MinValueValidator(5), MaxValueValidator(100)]) ab_final_stage = models.BooleanField(_(u'Финальная рассылка'), default=False) ab_final_choice = models.PositiveSmallIntegerField(_(u'Вариант в финальную рассылку'), choices=AB_FINAL_CHOICE, null=True, blank=True) mailing_list = models.ForeignKey(MailingList, verbose_name=_('mailing list'), null=True, blank=True) theme_for_filter = models.ForeignKey('theme.Theme', verbose_name=_(u'Ограничение по тематике'), null=True, blank=True) test_contacts = models.ManyToManyField(Contact, verbose_name=_('test contacts'), blank=True, null=True) server = models.ForeignKey(SMTPServer, verbose_name=_('smtp server'), default=lambda: SMTPServer.objects.get(id=DEFAULT_SMPTSERVER_ID)) header_sender = models.CharField(_('sender'), max_length=255, default=DEFAULT_HEADER_SENDER) header_reply = models.CharField(_('reply to'), max_length=255, default=DEFAULT_HEADER_REPLY) status = models.IntegerField(_('status'), choices=STATUS_CHOICES, default=DRAFT) sending_date = models.DateTimeField(_('sending date'), default=datetime.now) slug = models.SlugField(help_text=_('Used for displaying the newsletter on the site.'), unique=True) dailymail = models.BooleanField(_(u'Ежедневная рассылка'), blank=True, default=False) banner = models.ImageField(_(u'Баннер'), upload_to=gen_path, null=True, blank=True) banner_url = models.URLField(_(u'ссылка для баннера'), null=True, blank=True) creation_date = models.DateTimeField(_('creation date'), auto_now_add=True) modification_date = models.DateTimeField(_('modification date'), auto_now=True) def mails_sent(self): return self.contactmailingstatus_set.filter( status__in=[ContactMailingStatus.SENT, ContactMailingStatus.AB_WAITING], ).count() def can_cancel(self): return self.status in [self.WAITING, self.SENDING] @models.permalink def get_absolute_url(self): return ('newsletter_newsletter_preview', (self.slug,)) @models.permalink def get_historic_url(self): return ('newsletter_newsletter_historic', (self.slug,)) @models.permalink def get_statistics_url(self): return ('newsletter_newsletter_statistics', (self.slug,)) def get_status_text(self): for item in self.STATUS_CHOICES: if self.status == item[0]: return unicode(item[1]) return 'bug' # # def get_expedition_count(self): # add_sent = False # if self.dailymail: # qs = self.get_dailymail_subscribers() # add_sent = True # else: # qs = self.mailing_list.expedition_set() # if self.theme_for_filter: # # qs = qs # status_params = {'status': ContactMailingStatus.SENT} # if self.ab_testing and not self.ab_final_stage: # status_params = {'status__in': [ContactMailingStatus.SENT, ContactMailingStatus.AB_WAITING]} # another_mailing_sent = ContactMailingStatus.objects.filter( # newsletter__mailing_list=self.mailing_list, # creation_date__gte=datetime.now() - timedelta(days=5), # **status_params, # ).exclude(newsletter_id=self.pk).values_list('contact__id', flat=True) # qs = qs\ # .filter(contactsettings__theme=self.theme_for_filter)\ # .exclude(id__in=another_mailing_sent) # count = qs.count() # if add_sent: # count += self.mails_sent() # return count def clone(self): clone = copy.copy(self) clone.id = None clone.title+=u"_копия" clone.slug += "c" clone.status = Newsletter.DRAFT clone.ab_final_stage = False clone.ab_final_choice = None clone.save() cloned_attachments = [copy.copy(a) for a in list(self.attachment_set.all())] for a in cloned_attachments: a.id = None a.save() a.newsletter = clone a.save() clone.test_contacts = self.test_contacts.all() clone.save() return clone def __unicode__(self): return self.title class Meta: ordering = ('-creation_date',) verbose_name = _('newsletter') verbose_name_plural = _('newsletters') permissions = (('can_change_status', 'Can change status'),) def get_dailymail_subscribers(self): periodic_filter = [] periodic_filter.append(Q( periodic=Contact.PERIODIC_CHOICES.WEEK, last_mailing_date__lte=self.sending_date - timedelta(days=7), )) periodic_filter.append(Q( periodic=Contact.PERIODIC_CHOICES.WEEK_2, last_mailing_date__lte=self.sending_date - timedelta(days=14), )) month = relativedelta.relativedelta(month=1) periodic_filter.append(Q( periodic=Contact.PERIODIC_CHOICES.MONTH, last_mailing_date__lte=self.sending_date - month, )) periodic_filter.append(Q(last_mailing_date__isnull=True)) qs = Contact.objects.valid_subscribers().filter( reduce(operator.or_, periodic_filter), periodic_day=self.sending_date.isoweekday(), dailymailing=True, ) return qs class NewsletterRecommend(models.Model): theme = models.ManyToManyField('theme.Theme', verbose_name=_(u'Тема'), null=True, blank=True) exposition = models.ForeignKey('exposition.Exposition', verbose_name=_(u'Выставка'), null=True) conference = models.ForeignKey('conference.Conference', verbose_name=_(u'Конференция'), null=True) fr = models.DateField(_(u'Первый день показов'), default=lambda: date.today()) to = models.DateField(_(u'Последний день показов'), blank=True, null=True) creation_date = models.DateTimeField(_('creation date'), auto_now_add=True) def __unicode__(self): return u'{0} - {1}'.format(self.fr, self.to) class Meta: ordering = ('-creation_date',) verbose_name = _(u'Рекомендация') verbose_name_plural = _(u'Рекомендации') permissions = (('can_change_status', 'Can change status'),) def event(self): return self.exposition or self.conference class Link(models.Model): """Link sended in a newsletter""" title = models.CharField(_('title'), max_length=255) url = models.CharField(_('url'), max_length=255) creation_date = models.DateTimeField(_('creation date'), auto_now_add=True) def get_absolute_url(self): return self.url def __unicode__(self): return self.title class Meta: ordering = ('-creation_date',) verbose_name = _('link') verbose_name_plural = _('links') class Attachment(models.Model): """Attachment file in a newsletter""" def get_newsletter_storage_path(self, filename): filename = force_unicode(filename) return '/'.join([BASE_PATH, self.newsletter.slug, filename]) newsletter = models.ForeignKey(Newsletter, verbose_name=_('newsletter')) title = models.CharField(_('title'), max_length=255) file_attachment = models.FileField(_('file to attach'), max_length=255, upload_to=get_newsletter_storage_path) class Meta: verbose_name = _('attachment') verbose_name_plural = _('attachments') def __unicode__(self): return self.title def get_absolute_url(self): return self.file_attachment.url class ContactMailingStatus(models.Model): """Status of the reception""" SENT_TEST = -1 SENT = 0 ERROR = 1 INVALID = 2 OPENED = 4 OPENED_ON_SITE = 5 LINK_OPENED = 6 UNSUBSCRIPTION = 7 ANNOUNCE_NO_DATA = 8 AB_WAITING = 9 STATUS_CHOICES = ((SENT_TEST, _('sent in test')), (SENT, _('sent')), (ERROR, _('error')), (INVALID, _('invalid email')), (OPENED, _('opened')), (OPENED_ON_SITE, _('opened on site')), (LINK_OPENED, _('link opened')), (UNSUBSCRIPTION, _('unsubscription')), (ANNOUNCE_NO_DATA, _('announce no data')), (AB_WAITING, _('waiting for A-B final mailing')), ) newsletter = models.ForeignKey(Newsletter, verbose_name=_('newsletter')) contact = models.ForeignKey(Contact, verbose_name=_('contact')) status = models.IntegerField(_('status'), choices=STATUS_CHOICES) link = models.ForeignKey(Link, verbose_name=_('link'), blank=True, null=True) ab = models.PositiveSmallIntegerField(_(u'Вариант А-В'), choices=Newsletter.AB_FINAL_CHOICE, null=True, blank=True) creation_date = models.DateTimeField(_('creation date'), auto_now_add=True) def get_verbose_status(self): verbose = '' for s in self.STATUS_CHOICES: if s[0] == self.status: verbose = unicode(s[1]) break if self.status == ContactMailingStatus.LINK_OPENED: verbose +=": %s"% (self.link.url, self.link.url) return verbose def __unicode__(self): return '%s : %s : %s' % (self.newsletter.__unicode__(), self.contact.__unicode__(), self.get_status_display()) class Meta: ordering = ('-creation_date',) verbose_name = _('contact mailing status') verbose_name_plural = _('contact mailing statuses') class WorkGroup(models.Model): """Work Group for privatization of the ressources""" name = models.CharField(_('name'), max_length=255) group = models.ForeignKey(Group, verbose_name=_('permissions group')) contacts = models.ManyToManyField(Contact, verbose_name=_('contacts'), blank=True, null=True) mailinglists = models.ManyToManyField(MailingList, verbose_name=_('mailing lists'), blank=True, null=True) newsletters = models.ManyToManyField(Newsletter, verbose_name=_('newsletters'), blank=True, null=True) def __unicode__(self): return self.name class Meta: verbose_name = _('workgroup') verbose_name_plural = _('workgroups') class PopupCount(models.Model): theme = models.ForeignKey('theme.Theme', null=True) date = models.DateField(auto_created=True) cnt = models.PositiveIntegerField(default=0) class Meta: unique_together = ('theme', 'date') ordering = ['-date'] def get_subscr(self): qs = Contact.objects.filter(creation_date__startswith=self.date) return qs.count() def get_active(self): qs = Contact.objects.filter(creation_date__startswith=self.date, activated=True) return qs.count()