Beispiel #1
0
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)
Beispiel #2
0
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, )
Beispiel #3
0
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', )
Beispiel #4
0
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> &mdash; 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> &mdash; 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)
Beispiel #5
0
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
Beispiel #6
0
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', )
Beispiel #7
0
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', )
Beispiel #8
0
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, )
Beispiel #9
0
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)
Beispiel #10
0
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')
Beispiel #11
0
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, )
Beispiel #12
0
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])))
Beispiel #13
0
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)
Beispiel #14
0
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
Beispiel #15
0
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', )
Beispiel #16
0
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)
Beispiel #17
0
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', )
Beispiel #18
0
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', )
Beispiel #19
0
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', )
Beispiel #20
0
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
Beispiel #21
0
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', )
Beispiel #22
0
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', )
Beispiel #23
0
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
Beispiel #24
0
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', )
Beispiel #25
0
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', )
Beispiel #26
0
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')
Beispiel #27
0
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
Beispiel #28
0
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()
Beispiel #29
0
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
Beispiel #30
0
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()