class AbstractDelivery(models.Model): date = models.DateTimeField(auto_now_add=True, verbose_name=_('Date')) sender = fields.CharField(max_length=255, verbose_name=_('Sender')) recipients = fields.TextField(blank=True, verbose_name=_('Recipients')) other_recipients = fields.TextField(blank=True, verbose_name=_('Other Recipients')) hidden_recipients = fields.TextField(blank=True, verbose_name=_('Hidden Recipients')) subject = fields.CharField(blank=True, max_length=255, verbose_name=_('Subject')) html = fields.TextField(blank=True, verbose_name=_('HTML Version')) text = fields.TextField(blank=True, verbose_name=_('Plain Text Version')) class Meta: abstract = True ordering = ['-date'] verbose_name = _('Delivery') verbose_name_plural = _('Deliveries') def __str__(self): args = ( self.subject or ugettext('No subject'), date_format(self.date, 'SHORT_DATE_FORMAT'), ) return '{0} ({1})'.format(*args)
class AbstractCurrency(Enableable, Standard): symbol = fields.CharField( db_index=True, force_upper=True, max_length=7, verbose_name=_('Symbol'), help_text=_('Specify currency symbol, for example "€".')) code = fields.CharField( unique=True, charset='A-Z', force_upper=True, min_length=3, max_length=3, verbose_name=_('Code'), help_text=_('Specify 3-letter currency code, for example "EUR".')) number = fields.CharField( unique=True, charset='0-9', min_length=3, max_length=3, verbose_name=_('Number'), help_text=_('Specify numeric currency code, for example "978".')) # Additional info decimals = fields.SmallIntegerField( default=2, max_value=6, min_value=0, verbose_name=_('Decimals'), help_text=_('Number of digits after the decimal separator.')) countries = models.ManyToManyField( 'Country', blank=True, related_name='currencies', verbose_name=_('Countries'), help_text=_('Countries using this currency.')) objects = CurrencyManager() class Meta: abstract = True ordering = ['name'] verbose_name = _('Currency') verbose_name_plural = _('Currencies') @staticmethod def autocomplete_search_fields(): return ('name__icontains', 'code__iexact', 'number__iexact') def natural_key(self): return (self.code, )
class AbstractBlog(Illustrated, Slugged, MetaData, Logged): title = fields.CharField(unique=True, max_length=63, verbose_name=_('Title')) subtitle = fields.CharField( blank=True, max_length=255, verbose_name=_('Subtitle'), help_text=_('In a few words, explain what this site is about.')) description = fields.RichTextField( blank=True, verbose_name=_('Description'), help_text=_( 'Or, maybe, you want to write a more detailed explanation.')) authors = models.ManyToManyField('authors.Author', related_name='blogs', verbose_name=_('Authors')) objects = models.Manager() cache = LookupTable(indexed_fields=['slug'], prefetch_related=['authors', 'categories']) class Meta: abstract = True folder_name = 'blog_images' ordering = ['title'] verbose_name = _('Blog') verbose_name_plural = _('Blogs') def __str__(self): return self.title def get_absolute_url(self): kwargs = {'blog_slug': self.slug} return reverse('post_list', kwargs=kwargs) def get_feed_url(self): kwargs = {'blog_slug': self.slug} return full_reverse('post_feed', kwargs=kwargs) def get_upload_path(self, filename): filename = self.slug.replace('-', '_') return super(AbstractBlog, self).get_upload_path(filename) # GRAPPELLI SETTINGS @staticmethod def autocomplete_search_fields(): return ('title__icontains', )
class AbstractLink(Illustrated, Logged): blog = fields.CachedForeignKey('blogs.Blog', related_name='links', verbose_name=_('Blog')) name = fields.CharField( max_length=63, verbose_name=_('Name'), help_text=_('Example: A framework for perfectionists')) url = models.URLField( max_length=127, verbose_name=_('Web Address'), help_text= _("Example: <code>http://www.djangoproject.com/</code> — don't forget the <code>http://</code>" )) rss = models.URLField( blank=True, max_length=127, verbose_name=_('RSS Address'), help_text= _("Example: <code>http://www.djangoproject.com/rss.xml</code> — don't forget the <code>http://</code>" )) description = fields.CharField( max_length=255, blank=True, verbose_name=_('Description'), help_text= _('This will be shown when someone hovers the link in the blogroll, or optionally below the link.' )) category = fields.CachedForeignKey('LinkCategory', blank=True, null=True, on_delete=models.PROTECT, related_name='links', verbose_name=_('Category')) class Meta: abstract = True folder_name = 'blog_links' ordering = ['name'] verbose_name = _('Link') verbose_name_plural = _('Links') def __str__(self): return self.name def get_upload_path(self, filename): filename = slugify(self.name, ascii=True).replace('-', '_') return super(AbstractLink, self).get_upload_path(filename)
class AbstractRegion(Nestable, Standard): parent = ParentForeignKey('self', null=True, on_delete=models.CASCADE, related_name='children', verbose_name=_('Parent Region')) number = fields.CharField( unique=True, charset='0-9', min_length=3, max_length=3, verbose_name=_('Number'), help_text=_('Specify numeric region code, for example "150".')) objects = RegionManager() class Meta: abstract = True ordering = ['name'] verbose_name = _('Supranational Region') verbose_name_plural = _('Supranational Regions') @staticmethod def autocomplete_search_fields(): return ('name__icontains', 'number__iexact') def natural_key(self): return (self.number, ) @property def code(self): return self.number
class AbstractTag(Slugged): name = fields.CharField(unique=True, max_length=63, verbose_name=_('Name')) description = fields.TextField( blank=True, verbose_name=_('Description'), help_text=_('The description is usually not prominent.')) class Meta: abstract = True ordering = ['name'] verbose_name = _('Tag') verbose_name_plural = _('Tags') def __str__(self): return self.name def get_absolute_url(self): kwargs = {'tag_slug': self.slug} return reverse('post_list', kwargs=kwargs) def get_feed_url(self): kwargs = {'tag_slug': self.slug} return full_reverse('post_feed', kwargs=kwargs) # GRAPPELLI SETTINGS @staticmethod def autocomplete_search_fields(): return ('name__icontains', )
class AbstractDomain(models.Model): name = fields.CharField(editable=False, max_length=63, unique=True, validators=[RegexValidator(DOMAIN_RE)], verbose_name=_('Domain')) is_trusted = fields.BooleanField(default=False, verbose_name=_('Is Trusted?')) objects = models.Manager() cache = LookupTable(['name']) class Meta: abstract = True ordering = ['name'] verbose_name = _('E-mail Domain') verbose_name_plural = _('Domains') def __str__(self): return self.name @staticmethod def autocomplete_search_fields(): return ('name__icontains', )
class AbstractCountrySubdivision(Enableable, Standard): country = models.ForeignKey('Country', on_delete=models.CASCADE, related_name='subdivisions', verbose_name=_('Country')) code = fields.CharField( unique=True, charset='A-Z0-9\-', force_upper=True, min_length=4, max_length=6, verbose_name=_('Code'), help_text=_('Specify country subdivision code, for example "ES-O".')) objects = CountrySubdivisionManager() class Meta: abstract = True ordering = ['name'] verbose_name = _('Country Subdivision') verbose_name_plural = _('Country Subdivisions') @staticmethod def autocomplete_search_fields(): return ('name__icontains', 'code__iexact') def natural_key(self): return (self.code, )
class AbstractPage(models.Model): site = models.ForeignKey('sites.Site', on_delete=models.CASCADE, related_name='pages', verbose_name=_('Site')) path_head = fields.CharField(blank=True, max_length=255, verbose_name=_('Path Head')) path_tail = fields.CharField(max_length=63, verbose_name=_('Path Tail')) query_string = fields.CharField(blank=True, max_length=255, verbose_name=_('Query String')) objects = CurrentSiteManager() class Meta: abstract = True ordering = ['path_head', 'path_tail', 'query_string'] unique_together = [('site', 'path_head', 'path_tail', 'query_string')] verbose_name = _('Page') verbose_name_plural = _('Pages') def __str__(self): return self.full_path @staticmethod def autocomplete_search_fields(): return ('path_head__icontains', 'path_tail__icontains', 'query_string__icontains') def get_full_path(self): if self.query_string: return '{0}?{1}'.format(self.get_path(), self.query_string) else: return self.get_path() get_full_path.short_description = _('Full Path') def get_path(self): return '{0}{1}'.format(self.path_head, self.path_tail) get_path.short_description = _('Path') full_path = property(get_full_path) path = property(get_path)
class AbstractCommentStatus(Orderable): label = fields.CharField(max_length=63, verbose_name=_('Label')) api_id = fields.IdentifierField( unique=True, verbose_name=_('API Id'), help_text=_( 'This field is for internally identify the comment status. ' 'Can only contain lowercase letters, numbers and underscores.')) color = fields.ColorField( verbose_name=_('Color'), help_text=_('This color is used on the admin site for visually ' 'identify the comment status.')) publish_comment = fields.BooleanField( default=True, verbose_name=_('Publishes Comments'), help_text=_('Uncheck this box to make the comments effectively ' 'disappear from the blog.')) comment_replacement = fields.TextField( blank=True, verbose_name=_('Comment Replacement'), help_text=_('The content of this field will replace the text of ' 'the user comments. E.g.: "Inappropriate comment."')) objects = models.Manager() cache = LookupTable(indexed_fields=['api_id'], default_registry_key='comments:INITIAL_STATUS') class Meta: abstract = True verbose_name = _('Comment Status') verbose_name_plural = _('Comment Statuses') def __str__(self): return self.label def save(self, **kwargs): super(AbstractCommentStatus, self).save(**kwargs) self.comments.update(is_published=self.publish_comment) def natural_key(self): return (self.api_id, ) # CUSTOM METHODS def replace_comment(self): return bool(self.comment_replacement) replace_comment.admin_order_field = 'comment_replacement' replace_comment.boolean = True replace_comment.short_description = _('Replaces Comments') # GRAPPELLI SETTINGS @staticmethod def autocomplete_search_fields(): return ('label__icontains', 'api_id__icontains')
class AbstractCountry(Enableable, Standard): region = models.ForeignKey('Region', on_delete=models.CASCADE, related_name='countries', verbose_name=_('Region')) code = fields.CharField( unique=True, charset='A-Z', force_upper=True, min_length=2, max_length=2, verbose_name=_('Code'), help_text=_('Specify 2-letter country code, for example "ES".')) long_code = fields.CharField( unique=True, charset='A-Z', force_upper=True, min_length=3, max_length=3, verbose_name=_('Long Code'), help_text=_('Specify 3-letter country code, for example "ESP".')) number = fields.CharField( unique=True, charset='0-9', min_length=3, max_length=3, verbose_name=_('Number'), help_text=_('Specify numeric country code, for example "724".')) objects = CountryManager() class Meta: abstract = True ordering = ['name'] verbose_name = _('Country') verbose_name_plural = _('Countries') @staticmethod def autocomplete_search_fields(): return ('name__icontains', 'code__iexact', 'long_code__iexact', 'number__iexact') def natural_key(self): return (self.code, )
class Standard(models.Model): name = fields.CharField(unique=True, max_length=127, verbose_name=_('Native Name')) name_de = LocalizedNameField(verbose_name=_('German Name')) name_en = LocalizedNameField(verbose_name=_('English Name')) name_es = LocalizedNameField(verbose_name=_('Spanish Name')) name_fr = LocalizedNameField(verbose_name=_('French Name')) name_pt = LocalizedNameField(verbose_name=_('Portuguese Name')) name_ru = LocalizedNameField(verbose_name=_('Russian Name')) name_zh = LocalizedNameField(verbose_name=_('Chinese Name')) class Meta: abstract = True def __init__(self, *args, **kwargs): for attr_name in six.iterkeys(kwargs): if NAME_RE.search(attr_name) is not None: attr_value = kwargs.pop(attr_name) kwargs[''.join(('_', attr_name))] = attr_value super(Standard, self).__init__(*args, **kwargs) def __getattr__(self, attr_name): if NAME_RE.search(attr_name) is None: raise MissingAttributeError(self, attr_name) name = self.__dict__.get(''.join(('_', attr_name))) if name: return name fallback = settings.STANDARDS_FALLBACK_TRANSLATION if fallback != 'native': name = self.__dict__.get(''.join(('_name_', fallback))) if name: return name return self.name def __setattr__(self, attr_name, attr_value): if (attr_name.startswith('_') or NAME_RE.search(attr_name) is None): super(Standard, self).__setattr__(attr_name, attr_value) else: self.__dict__[''.join(('_', attr_name))] = attr_value def __str__(self): if settings.STANDARDS_DEFAULT_TRANSLATION == 'native': return self.name if settings.STANDARDS_DEFAULT_TRANSLATION == 'locale': language = translation.get_language() else: language = settings.STANDARDS_DEFAULT_TRANSLATION return getattr(self, ''.join(('name_', language[:2])))
class Parameter(Nestable): parent = ParentForeignKey('self', null=True, on_delete=models.CASCADE, related_name='children', verbose_name=_('Parent')) index = fields.IntegerField(null=True, blank=True, min_value=0, verbose_name=_('Index')) name = fields.CharField(unique=True, max_length=63, verbose_name=_('Name')) token = fields.CharField( max_length=255, verbose_name=_('Token'), help_text=_('Used to check the User-Agent strings.')) regex = fields.BooleanField( default=False, verbose_name=_('Regular Expression'), help_text=_('Check this if your token is a regular expression.')) objects = ParameterManager() class Meta: abstract = True def __str__(self): return self.name @staticmethod def autocomplete_search_fields(): return ('name__icontains', ) def verify(self, user_agent_string): token = self.token.lower() ua = user_agent_string.lower() if self.regex: return (re.search(token, ua) is not None) else: return (token in ua)
class AbstractSource(models.Model): name = fields.CharField(unique=True, max_length=255, verbose_name=_('Name')) last_modified = models.DateTimeField(default=timezone.now, verbose_name=_('Last Modified')) class Meta: abstract = True ordering = ['name'] verbose_name = _('Source') verbose_name_plural = _('Sources') def __str__(self): return self.name
class AbstractUnsubscriptionReason(Orderable): description = fields.CharField(max_length=255, verbose_name=_('Description')) class Meta: abstract = True verbose_name = _('Unsubscription Reason') verbose_name_plural = _('Unsubscription Reasons') def __str__(self): return self.description @staticmethod def autocomplete_search_fields(): return ('description__icontains', )
class AbstractMessage(Logged, Slugged): newsletter = models.ForeignKey('Newsletter', on_delete=models.CASCADE, related_name='messages', verbose_name=_('Newsletter')) guid = fields.GuidField(max_length=15, editable=False, unique=True, verbose_name=_('Global Unique Identifier')) subject = fields.CharField(max_length=255, verbose_name=_('Subject')) html = fields.TextField(verbose_name=_('HTML Version')) text = fields.TextField(blank=True, verbose_name=_('Plain Text Version')) is_sent = fields.BooleanField(default=False, editable=False, verbose_name=_('Is Sent?')) class Meta: abstract = True verbose_name = _('Message') verbose_name_plural = _('Messages') def __str__(self): return self.subject @staticmethod def autocomplete_search_fields(): return ('subject__icontains', ) def get_absolute_url(self): kwargs = { #'message_pk': self.pk, #'message_slug': self.slug, 'message_guid': self.guid, } return reverse('message', kwargs=kwargs) def save(self, **kwargs): if not self.text: self.text = extract_text(self.html) super(AbstractMessage, self).save(**kwargs)
class AbstractReferrer(models.Model): domain = fields.CharField(unique=True, max_length=63, verbose_name=_('Domain')) class Meta: abstract = True ordering = ['domain'] verbose_name = _('Referrer') verbose_name_plural = _('Referrers') def __str__(self): return self.domain @staticmethod def autocomplete_search_fields(): return ('domain__icontains', )
class AbstractAttachmentCategory(models.Model): name = fields.CharField(unique=True, max_length=63, verbose_name=_('Name')) description = fields.TextField(blank=True, verbose_name=_('Description')) class Meta: abstract = True ordering = ['name'] verbose_name = _('Attachment Category') verbose_name_plural = _('Attachment Categories') def __str__(self): return self.name # GRAPPELLI SETTINGS @staticmethod def autocomplete_search_fields(): return ('name__icontains', )
class AbstractCategory(Illustrated, Slugged, MetaData): blog = fields.CachedForeignKey('blogs.Blog', related_name='categories', verbose_name=_('Blog')) name = fields.CharField(unique=True, max_length=63, verbose_name=_('Name')) description = fields.TextField( blank=True, verbose_name=_('Description'), help_text=_('The description is usually not prominent.')) class Meta: abstract = True folder_name = 'blog_categories' ordering = ['name'] verbose_name = _('Category') verbose_name_plural = _('Categories') def __str__(self): return self.name def get_absolute_url(self): kwargs = {'category_slug': self.slug} if settings.BLOG_MULTIPLE: kwargs['blog_slug'] = self.blog.slug return reverse('post_list', kwargs=kwargs) def get_feed_url(self): kwargs = {'category_slug': self.slug} if settings.BLOG_MULTIPLE: kwargs['blog_slug'] = self.blog.slug return full_reverse('post_feed', kwargs=kwargs) def get_upload_path(self, filename): filename = self.slug.replace('-', '_') return super(AbstractCategory, self).get_upload_path(filename) # GRAPPELLI SETTINGS @staticmethod def autocomplete_search_fields(): return ('name__icontains', )
class AbstractThumbnail(models.Model): source = models.ForeignKey('Source', on_delete=models.CASCADE, related_name='thumbnails', verbose_name=_('Source')) name = fields.CharField(max_length=255, verbose_name=_('Name')) last_modified = models.DateTimeField(default=timezone.now, verbose_name=_('Last Modified')) class Meta: abstract = True ordering = ['source', 'name'] unique_together = [('source', 'name')] verbose_name = _('Thumbnail') verbose_name_plural = _('Thumbnails') def __str__(self): return self.name
class AbstractSubscriberTag(Logged): name = fields.CharField(unique=True, max_length=63, verbose_name=_('Name')) description = fields.TextField(blank=True, verbose_name=_('Description')) objects = models.Manager() cache = LookupTable(['name']) class Meta: abstract = True ordering = ['name'] verbose_name = _('Subscriber Tag') verbose_name_plural = _('Subscriber Tags') def __str__(self): return self.name @staticmethod def autocomplete_search_fields(): return ('name__icontains', )
class AbstractReferrerPage(models.Model): referrer = models.ForeignKey('metrics.Referrer', on_delete=models.CASCADE, related_name='pages', verbose_name=_('Referrer')) full_path = fields.CharField(max_length=255, verbose_name=_('Path')) class Meta: abstract = True ordering = ['full_path'] unique_together = [('referrer', 'full_path')] verbose_name = _('Referrer page') verbose_name_plural = _('Referrer pages') def __str__(self): return self.full_path @staticmethod def autocomplete_search_fields(): return ('path__icontains', )
class AbstractVisitor(models.Model): site = models.ForeignKey('sites.Site', on_delete=models.CASCADE, related_name='visitors', verbose_name=_('Site')) is_authenticated = fields.BooleanField(verbose_name=_('Is Authenticated?')) key = fields.CharField(max_length=32, db_index=True, verbose_name=_('Visitor Key')) objects = CurrentSiteManager() class Meta: abstract = True ordering = ['-id'] verbose_name = _('Visitor') verbose_name_plural = _('Visitors') def __str__(self): return self.key
class AbstractLinkCategory(Slugged): blog = fields.CachedForeignKey('blogs.Blog', related_name='link_categories', verbose_name=_('Blog')) name = fields.CharField(unique=True, max_length=63, verbose_name=_('Name')) description = fields.TextField( blank=True, verbose_name=_('Description'), help_text=_('The description is usually not prominent.')) objects = models.Manager() cache = LookupTable(indexed_fields=['slug'], default_registry_key='links:DEFAULT_CATEGORY') class Meta: abstract = True ordering = ['name'] verbose_name = _('Link Category') verbose_name_plural = _('Link Categories') def __str__(self): return self.name def get_absolute_url(self): kwargs = {'link_category_slug': self.slug} if settings.BLOG_MULTIPLE: kwargs['blog_slug'] = self.blog.slug return reverse('link_list', kwargs=kwargs) # GRAPPELLI SETTINGS @staticmethod def autocomplete_search_fields(): return ('name__icontains', )
class AbstractConnection(Logged): name = fields.CharField(max_length=63, unique=True, verbose_name=_('Name')) host = fields.CharField(max_length=255, verbose_name=_('Host'), help_text=_('Address of the SMTP server.')) port = fields.IntegerField(default=25, min_value=0, verbose_name=_('Port')) username = fields.CharField(max_length=255, verbose_name=_('Username')) password = fields.EncryptedCharField(max_length=255, verbose_name=_('Password')) is_secure = fields.BooleanField( default=False, verbose_name=_('Use TLS?'), help_text= _('Whether to use a secure connection when talking to the SMTP server.' )) is_logged = fields.BooleanField( default=False, verbose_name=_('Store Mails?'), help_text=_('Whether to store a copy of each sent mail.')) objects = models.Manager() cache = LookupTable() class Meta: abstract = True ordering = ['name'] verbose_name = _('E-mail Connection') verbose_name_plural = _('Connections') _connection = None def __str__(self): return '{0} ({1})'.format(self.name, self.host) def save(self, **kwargs): """ Saves the record back to the database. Take into account that this closes the network connection if it was open. """ self.close() self._connection = None super(AbstractConnection, self).save(**kwargs) # CUSTOM METHODS def close(self): """ Closes the connection to the email server. """ if self._connection is not None: self._connection.close() def open(self): """ Ensures we have a connection to the email server. Returns whether or not a new connection was required (True or False). """ return self.smtp_connection.open() def send_messages(self, email_messages): """ Sends one or more EmailMessage objects and returns the number of email messages sent. """ return self.smtp_connection.send_messages(email_messages) # PROPERTIES @property def smtp_connection(self): """ Returns an instance of the SMTP email backend. This instance uses the authentication credentials set in the record to connect the SMTP server. """ if self._connection is None: if self.is_logged: backend = 'yepes.contrib.emails.backends.LoggedSmtpBackend' else: backend = 'yepes.contrib.emails.backends.SmtpBackend' self._connection = mail.get_connection( backend, **{ 'host': self.host, 'port': self.port, 'username': self.username, 'password': self.password, 'use_tls': self.is_secure, }) return self._connection # GRAPPELLI SETTINGS @staticmethod def autocomplete_search_fields(): return ('host__icontains', )
class AbstractMessage(Logged): connection = fields.CachedForeignKey('Connection', on_delete=models.CASCADE, related_name='messages', verbose_name=_('Connection')) name = fields.CharField(unique=True, max_length=63, verbose_name=_('Name')) sender_name = fields.CharField(max_length=127, verbose_name=_("Sender's Name")) sender_address = fields.CharField(max_length=127, verbose_name=_("Sender's Address")) recipient_name = fields.CharField(blank=True, max_length=255, verbose_name=_("Recipient's Name")) recipient_address = fields.CharField( blank=True, max_length=255, verbose_name=_("Recipient's Address"), help_text=_( 'If field is blank, it will be populated when the message is sent.' )) reply_to_name = fields.CharField(blank=True, max_length=127, verbose_name=_("Reply To Name")) reply_to_address = fields.CharField(blank=True, max_length=127, verbose_name=_("Reply To Address")) subject = fields.CharField(max_length=255, verbose_name=_('Subject')) html = fields.TextField(verbose_name=_('HTML Version')) text = fields.TextField(blank=True, verbose_name=_('Plain Text Version')) class Meta: abstract = True ordering = ['name'] verbose_name = _('Message') verbose_name_plural = _('Messages') def __str__(self): return self.name def save(self, **kwargs): if not self.text: self.text = extract_text(self.html) super(AbstractMessage, self).save(**kwargs) # CUSTOM METHODS def render(self, context=None): if not isinstance(context, Context): context = Context(context) email = EmailMultiAlternatives( Template(self.subject).render(context), Template(self.text).render(context), self.sender, self.recipient, connection=self.connection, ) email.attach_alternative( Template(self.html).render(context), 'text/html', ) if self.reply_to_address: email.extra_headers['Reply-To'] = self.reply_to return email def render_and_send(self, recipient_list, reply_to=None, context=None, connection=None): to = [] for recipient in recipient_list: if isinstance(recipient, (tuple, list)): name = recipient[0].strip() address = recipient[1].strip() if name: to.append('"{0}" <{1}>'.format(name, address)) else: to.append(address) else: to.append(recipient) if isinstance(reply_to, (tuple, list)): name = reply_to[0].strip() address = reply_to[1].strip() if name: reply_to = '"{0}" <{1}>'.format(name, address) else: reply_to = address email = self.render(context) if not email.to: email.to = to if 'Reply-To' not in email.extra_headers and reply_to: email.extra_headers['Reply-To'] = reply_to if connection is not None: email.connection = connection email.send() # PROPERTIES @described_property(_('Recipient'), cached=True) def recipient(self): recipient_list = [] if self.recipient_address.strip(): name_list = self.recipient_name.split(',') address_list = self.recipient_address.split(',') for i, address in enumerate(address_list): address = address.strip() if len(name_list) > i: name = name_list[i].strip() recipient_list.append('"{0}" <{1}>'.format(name, address)) else: recipient_list.append(address) return recipient_list @described_property(_('Reply To'), cached=True) def reply_to(self): if self.reply_to_name: return '"{0}" <{1}>'.format(self.reply_to_name, self.reply_to_address) elif self.reply_to_address: return self.reply_to_address else: return None @described_property(_('Sender'), cached=True) def sender(self): if self.sender_name: return '"{0}" <{1}>'.format(self.sender_name, self.sender_address) elif self.sender_address: return self.sender_address else: return None # GRAPPELLI SETTINGS @staticmethod def autocomplete_search_fields(): return ('name__icontains', 'subject__icontains')
class AbstractAttachment(Logged, Calculated): guid = fields.GuidField(editable=False, verbose_name=_('Global Unique Identifier')) title = fields.CharField(max_length=63, verbose_name=_('Title')) caption = fields.CharField(max_length=255, blank=True, verbose_name=_('Caption')) alt = fields.CharField(max_length=127, blank=True, verbose_name=_('Alternate Text')) description = fields.TextField(blank=True, verbose_name=_('Description')) file = models.FileField(blank=True, max_length=127, upload_to=file_upload_to, verbose_name=_('File')) external_file = models.URLField(blank=True, max_length=127, verbose_name=_('External File')) size = fields.IntegerField(blank=True, calculated=True, min_value=0, null=True, verbose_name=_('Size')) mime_type = fields.CharField(blank=True, calculated=True, max_length=31, null=True, verbose_name=_('MIME Type')) height = fields.IntegerField(blank=True, calculated=True, min_value=0, null=True, verbose_name=_('Height')) width = fields.IntegerField(blank=True, calculated=True, min_value=0, null=True, verbose_name=_('Width')) category = models.ForeignKey('AttachmentCategory', blank=True, null=True, on_delete=models.PROTECT, related_name='attachments', verbose_name=_('Category')) class Meta: abstract = True folder_name = 'attachments' ordering = ['title'] verbose_name = _('Attachment') verbose_name_plural = _('Attachments') def __str__(self): return self.title def clean(self): super(AbstractAttachment, self).clean() if not self.file and not self.external_file: msg = _( 'You must upload a file or set the URL of an external file.') raise ValidationError({'file': msg}) def delete(self, *args, **kwargs): self.file.delete(save=False) return super(AbstractAttachment, self).delete(*args, **kwargs) def get_upload_path(self, filename): if self.title: _, extension = os.path.splitext(filename) return slugify(self.title, ascii=True).replace('-', '_') + extension else: return filename # CUSTOM METHODS def calculate_height(self): if not self.file or not self.is_image: return None else: return self.image.height def calculate_mime_type(self): if self.file and magic is not None: file_type = magic.from_buffer(self.file.read(1024), mime=True) if file_type is not None: return force_text(file_type) file_name = self.get_file_name() file_type, _ = mimetypes.guess_type(file_name) if file_type is not None: return force_text(file_type) if not self.file and magic is not None: file_type = magic.from_buffer(urlopen(file_name).read(1024), mime=True) if file_type is not None: return force_text(file_type) return None def calculate_size(self): if not self.file: return None else: return self.file.size def calculate_width(self): if not self.file or not self.is_image: return None else: return self.image.width def get_audio_tag(self, **attrs): wrap = attrs.pop('wrap', False) attrs['src'] = self.get_file_url() attrs.setdefault('controls', True) attrs.setdefault('preload', 'none') content = self.get_file_link(text=(self.alt or self.title)) tag = make_double_tag('audio', content, attrs) if wrap: tag = make_double_tag('div', tag, {'class': 'audio-wrap'}) return tag def get_display_size(self): if self.size is None: return '' bytes = self.size if bytes < 1024: return '{0} B'.format(number_format(bytes)) kb = (bytes / 1024) if kb < 1024: return '{0} KB'.format(number_format(kb, 1)) mb = (kb / 1024) if mb < 1024: return '{0} MB'.format(number_format(mb, 1)) gb = (mb / 1024) return '{0} GB'.format(number_format(gb, 1)) get_display_size.admin_order_field = 'size' get_display_size.short_description = _('Size') def get_file_link(self, **attrs): attrs['href'] = self.get_file_url() attrs.setdefault('download', True) content = attrs.pop('text', self.title) return make_double_tag('a', content, attrs) def get_file_url(self): if not self.file: return self.external_file else: return self.file.url get_file_url.short_description = _('File') def get_file_name(self): if not self.file: return self.external_file else: return self.file.name get_file_name.short_description = _('File') def get_tag(self, **attrs): if self.is_audio: return self.get_audio_tag(**attrs) if self.is_image: return self.get_image_tag(**attrs) if self.is_video: return self.get_video_tag(**attrs) if self.is_external: url = self.get_file_url() if 'youtube' in url or 'vimeo' in url: return self.get_iframe_tag() return self.get_file_link(**attrs) def get_iframe_tag(self, **attrs): wrap = attrs.pop('wrap', False) attrs['src'] = self.get_file_url() attrs.setdefault('width', self.width or 640) attrs.setdefault('height', self.height or 360) attrs.setdefault('frameborder', 0) attrs.setdefault('webkitallowfullscreen', True) attrs.setdefault('mozallowfullscreen', True) attrs.setdefault('allowfullscreen', True) content = self.get_file_link(text=(self.alt or self.title)) tag = make_double_tag('iframe', content, attrs) if wrap: tag = make_double_tag('div', tag, {'class': 'iframe-wrap'}) return tag def get_image_tag(self, **attrs): wrap = attrs.pop('wrap', False) attrs['src'] = self.get_file_url() if self.width: attrs.setdefault('width', self.width) if self.height: attrs.setdefault('height', self.height) attrs.setdefault('alt', self.alt or self.title) tag = make_single_tag('img', attrs) if wrap: tag = make_double_tag('div', tag, {'class': 'image-wrap'}) return tag def get_video_tag(self, **attrs): wrap = attrs.pop('wrap', False) attrs['src'] = self.get_file_url() attrs.setdefault('width', self.width or 640) attrs.setdefault('height', self.height or 360) attrs.setdefault('controls', True) attrs.setdefault('preload', 'metadata') content = self.get_file_link(text=(self.alt or self.title)) tag = make_double_tag('video', content, attrs) if wrap: tag = make_double_tag('div', tag, {'class': 'video-wrap'}) return tag # PROPERTIES @cached_property def image(self): if not self.file: return None else: return SourceFile(self.file, self.file.name, self.file.storage) @cached_property def is_audio(self): file_url = self.get_file_url() if file_url and file_url.endswith(settings.AUDIO_EXTENSIONS): return True if self.mime_type and self.mime_type.startswith('audio'): return True return False @cached_property def is_external(self): return not self.file @cached_property def is_image(self): file_url = self.get_file_url() if file_url and file_url.endswith(settings.IMAGE_EXTENSIONS): return True if self.mime_type and self.mime_type.startswith('image'): return True return False @cached_property def is_video(self): file_url = self.get_file_url() if file_url and file_url.endswith(settings.VIDEO_EXTENSIONS): return True if self.mime_type and self.mime_type.startswith('video'): return True return False
class AbstractConfiguration(Logged): ALGORITHM_CHOICES = ( ('undefined', 'undefined'), ('sample', 'sample'), ('liquid', 'liquid'), ) + tuple( (algorithm, algorithm) for algorithm in AVAILABLE_ALGORITHMS if algorithm != 'undefined') FORMAT_CHOICES = ( ('GIF', 'GIF'), ('JPEG', 'JPEG'), ('PNG8', 'PNG8'), ('PNG64', 'PNG64'), ('WEBP', 'WEBP'), ) GRAVITY_CHOICES = ( ('north_west', _('Northwest')), ('north', _('North')), ('north_east', _('Northeast')), ('west', _('West')), ('center', _('Center')), ('east', _('East')), ('south_west', _('Southwest')), ('south', _('South')), ('south_east', _('Southeast')), ) MODE_CHOICES = ( ('scale', _('Scale')), ('fit', _('Fit')), ('limit', _('Fit without enlarging')), ('fill', _('Fill')), ('lfill', _('Fill without enlarging')), ('pad', _('Pad')), ('lpad', _('Pad without enlarging')), ('crop', _('Crop')), ) key = fields.IdentifierField(max_length=63, unique=True, verbose_name=_('Key')) width = fields.IntegerField(min_value=0, verbose_name=_('Width')) height = fields.IntegerField(min_value=0, verbose_name=_('Height')) background = fields.ColorField(blank=True, null=True, verbose_name=_('Background')) mode = fields.CharField(choices=MODE_CHOICES, default='limit', max_length=15, verbose_name=_('Crop Mode')) algorithm = fields.CharField(choices=ALGORITHM_CHOICES, default='undefined', max_length=15, verbose_name=_('Resizing Algorithm')) gravity = fields.CharField(choices=GRAVITY_CHOICES, default='center', max_length=15, verbose_name=_('Gravity')) format = fields.CharField(choices=FORMAT_CHOICES, default='JPEG', max_length=15, verbose_name=_('Format')) quality = fields.IntegerField(default=85, max_value=100, min_value=1, verbose_name=_('Quality')) objects = models.Manager() cache = LookupTable(['key']) class Meta: abstract = True ordering = ['key'] verbose_name = _('Thumbnail Configuration') verbose_name_plural = _('Configurations') def __str__(self): return self.key @staticmethod def autocomplete_search_fields(): return ('key__icontains', ) def clean(self): if not self.width and not self.height: raise ValidationError()
class AbstractNewsletter(Orderable, Logged, Slugged, MetaData): """ A regularly distributed publication to which subscribers can subscribe. """ connection = fields.CachedForeignKey('emails.Connection', on_delete=models.CASCADE, related_name='newsletters', verbose_name=_('E-mail Connection')) guid = fields.GuidField(max_length=7, editable=False, unique=True, verbose_name=_('Global Unique Identifier')) name = fields.CharField(unique=True, max_length=63, verbose_name=_('Name')) description = fields.RichTextField(blank=True, verbose_name=_('Description')) is_published = fields.BooleanField(default=True, verbose_name=_('Is Published?')) sender_name = fields.CharField(max_length=127, verbose_name=_("Sender's Name")) sender_address = fields.CharField(max_length=127, verbose_name=_("Sender's Address")) reply_to_name = fields.CharField(blank=True, max_length=127, verbose_name=_("Reply To Name")) reply_to_address = fields.CharField(blank=True, max_length=127, verbose_name=_("Reply To Address")) return_path_name = fields.CharField(blank=True, max_length=127, verbose_name=_("Return To Name")) return_path_address = fields.CharField(blank=True, max_length=127, verbose_name=_("Return To Address")) objects = NewsletterManager() cache = LookupTable(['guid', 'name']) class Meta: abstract = True verbose_name = _('Newsletter') verbose_name_plural = _('Newsletters') def __str__(self): return self.name @staticmethod def autocomplete_search_fields(): return ('name__icontains', ) # CUSTOM METHODS def get_default_meta_index(self): if self.is_published: return super(AbstractNewsletter, self).get_default_meta_index() else: return False # PROPERTIES @described_property(_('Reply To')) def reply_to(self): if self.reply_to_name: return '"{0}" <{1}>'.format(self.reply_to_name, self.reply_to_address) elif self.reply_to_address: return self.reply_to_address else: return None @described_property(_('Return Path')) def return_path(self): if self.return_path_name: return '"{0}" <{1}>'.format(self.return_path_name, self.return_path_address) elif self.return_path_address: return self.return_path_address else: return None @described_property(_('Sender')) def sender(self): if self.sender_name: return '"{0}" <{1}>'.format(self.sender_name, self.sender_address) elif self.sender_address: return self.sender_address else: return None
class AbstractSubscriber(Enableable, Logged): guid = fields.GuidField(max_length=31, editable=False, unique=True, verbose_name=_('Global Unique Identifier')) email_address = fields.EmailField(max_length=127, unique=True, verbose_name=_('E-mail Address')) email_domain = models.ForeignKey('Domain', editable=False, on_delete=models.CASCADE, related_name='subscribers', verbose_name=_('E-mail Domain')) first_name = fields.CharField(blank=True, max_length=63, verbose_name=_('First Name')) last_name = fields.CharField(blank=True, max_length=63, verbose_name=_('Last Name')) newsletters = models.ManyToManyField('Newsletter', through='Subscription', related_name='subscribers', verbose_name=_('Newsletters')) tags = models.ManyToManyField('SubscriberTag', blank=True, related_name='subscribers', verbose_name=_('Tags')) score = fields.FloatField(blank=True, db_index=True, default=2.0, editable=False, verbose_name=_('Score')) class Meta: abstract = True ordering = ['email_address'] verbose_name = _('Subscriber') verbose_name_plural = _('Subscribers') def __str__(self): return self.email_address @staticmethod def autocomplete_search_fields(): return ('email_address__icontains', 'first_name__icontains', 'last_name__icontains') # CUSTOM METHODS def is_subscribed_to(self, newsletter): if not self._get_pk_val(): return False else: return self.subscriptions.filter(newsletter=newsletter).exists() def set_email(self, address): address = normalize_email(address) if not validate_email(address): msg = "'{0}' is not a valid email address." raise ValueError(msg.format(address)) _, domain_name = address.rsplit('@', 1) domain, _ = Domain.objects.get_or_create(name=domain_name) self.email_address = address self.email_domain = domain def resubscribe_to(self, newsletter): if not self.is_subscribed_to(newsletter): qs = self.unsubscriptions.filter(newsletter=newsletter) unsubscription = qs.order_by('date').last() if unsubscription is not None: unsubscription.delete() return self.subscriptions.create(newsletter=newsletter) def subscribe_to(self, newsletter): if self.is_subscribed_to(newsletter): return None else: return self.subscriptions.create(newsletter=newsletter) def unsubscribe_from(self, newsletter, reason=None, last_message=None): if self.is_subscribed_to(newsletter): self.subscriptions.filter(newsletter=newsletter).delete() kwargs = { 'newsletter': newsletter, 'reason': reason, 'last_message': last_message, } return self.unsubscriptions.create(**kwargs) # PROPERTIES @described_property(_('Name')) def full_name(self): return ' '.join(( self.first_name, self.last_name, )).strip()