You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

869 lines
35 KiB

# -*- 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 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 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
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)
moscow = models.BooleanField(_(u'Москва'), blank=True, default=True)
russia = models.BooleanField(_(u'Россия'), blank=True, default=True)
r_cities = models.ManyToManyField('city.City', blank=True, null=True, verbose_name=_(u'Города России'))
foreign = models.BooleanField(_(u'Зарубеж'), blank=True, default=True)
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'Тематики'))
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_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__gte=date,
to__lte=date)
recommended = SearchQuerySet().models(Exposition, Conference)\
.filter(expohit=True, data_begin__gte=date)
if th_tg_filter:
paid_recommend = list(paid_recommend.filter(theme__in=th_tg_structure.keys()))
recommended = recommended.filter(th_tg_filter)
try:
ctx['recommended'] = choice(paid_recommend)
except:
try:
ctx['recommended'] = recommended[0]
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.r_cities.values_list('pk', flat=True))
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.area.order_by('pk').values_list('pk', flat=True))
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)
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 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=self.sending_date - timedelta(days=7),
))
periodic_filter.append(Q(
periodic=Contact.PERIODIC_CHOICES.WEEK_2,
last_mailing_date=self.sending_date - timedelta(days=14),
))
month = relativedelta.relativedelta(month=1)
periodic_filter.append(Q(
periodic=Contact.PERIODIC_CHOICES.MONTH,
last_mailing_date=self.sending_date - month,
))
periodic_filter.append(Q(last_mailing_date__isnull=True))
qs = Contact.objects.subscribers().filter(
reduce(operator.or_, periodic_filter),
periodic_day=self.sending_date.isoweekday(),
from_users=False)
return qs
class NewsletterRecommend(models.Model):
theme = models.ManyToManyField('theme.Theme', null=True)
exposition = models.ForeignKey('exposition.Exposition', verbose_name=_(u'Выставка'), null=True)
conference = models.ForeignKey('conference.Conference', verbose_name=_(u'Конференция'), null=True)
fr = models.DateField(default=lambda: date.today())
to = models.DateField(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 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 +=": <a href='%s'>%s</a>"% (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()