class Illustrated(models.Model): image = fields.ImageField( blank=True, max_length=127, upload_to=image_upload_to, height_field='image_height', width_field='image_width', verbose_name=_('Image')) image_height = fields.IntegerField( blank=True, editable=False, min_value=0, null=True, verbose_name=_('Image Height')) image_width = fields.IntegerField( blank=True, editable=False, min_value=0, null=True, verbose_name=_('Image Width')) class Meta: abstract = True def __str__(self): return os.path.basename(self.image.name) def get_upload_path(self, filename): foldername = getattr(self._meta, 'folder_name', None) if not foldername: msg = 'You must set a ``folder_name`` in the model Meta class.' raise ImproperlyConfigured(msg) else: return os.path.join(foldername, filename[0], filename)
class AbstractPageView(models.Model): visit = models.ForeignKey('metrics.Visit', on_delete=models.CASCADE, related_name='page_views', verbose_name=_('Visit')) page = models.ForeignKey('metrics.Page', on_delete=models.CASCADE, related_name='page_views', verbose_name=_('Page')) previous_page_id = fields.IntegerField(blank=True, null=True, verbose_name=_('Previous Page ID')) next_page_id = fields.IntegerField(blank=True, null=True, verbose_name=_('Next Page ID')) status_code = fields.SmallIntegerField(verbose_name=_('Status Code')) date = models.DateTimeField(verbose_name=_('Date')) load_time = fields.FloatField(verbose_name=_('Load Time')) class Meta: abstract = True ordering = ['-date'] verbose_name = _('Page View') verbose_name_plural = _('Page Views') _next_page_cache = Undefined _previous_page_cache = Undefined def get_next_page(self): if self._next_page_cache is Undefined: if self.next_page_id is None: self._next_page_cache = None else: self._next_page_cache = Page.objects.get(pk=self.next_page_id) return self._next_page_cache get_next_page.short_description = _('Next Page') def get_previous_page(self): if self._previous_page_cache is Undefined: if self.previous_page_id is None: self._previous_page_cache = None else: self._previous_page_cache = Page.objects.get( pk=self.previous_page_id) return self._previous_page_cache get_previous_page.short_description = _('Previous Page') next_page = property(get_next_page) previous_page = property(get_previous_page)
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 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 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 Orderable(six.with_metaclass(OrderableBase, models.Model)): """ Abstract model that provides a custom ordering integer field similar to using Meta's ``order_with_respect_to``, since to date (Django 1.2) this doesn't work with ``ForeignKey('self')``. We may also want this feature for models that aren't ordered with respect to a particular field. """ # Do not set ``db_index`` here because ``order_with_respect_to``. index = fields.IntegerField(blank=True, min_value=0, verbose_name=_('Index')) class Meta: abstract = True _next = Undefined _previous = Undefined def get_queryset(self): """ Returns a dict to use as a filter for ordering operations containing the original `Meta.order_with_respect_to` value if provided. """ qs = self.__class__._default_manager.get_queryset() if self._meta.filter_field: name = self._meta.filter_field value = getattr(self, name) return qs.filter(**{name: value}) else: return qs def delete(self, *args, **kwargs): """ Update the ordering values for siblings. """ if self.index is not None: qs = self.get_queryset().filter(index__gt=self.index) qs.update(index=F('index') - 1) super(Orderable, self).delete(*args, **kwargs) def get_next_in_order(self): if self._next is Undefined: qs = self.get_queryset() qs = qs.filter(index__gt=self.index) qs = qs.order_by('index') self._next = qs.first() return self._next def get_previous_in_order(self): if self._previous is Undefined: qs = self.get_queryset() qs = qs.filter(index__lt=self.index) qs = qs.order_by('index') self._previous = qs.last() return self._previous def save(self, **kwargs): """ Set the initial ordering value. """ updated_fields = kwargs.get('update_fields', ()) new_record = (kwargs.get('force_insert', False) or not (self._get_pk_val() or updated_fields)) self._next = Undefined self._previous = Undefined if new_record and not self.index: qs = self.get_queryset() self.index = qs.filter(index__isnull=False).count() super(Orderable, self).save(**kwargs)
class BaseComment(Nestable, Logged, Linked): parent = ParentForeignKey('self', blank=True, null=True, related_name='children', verbose_name=_('Parent')) author_name = fields.CharField(blank=False, max_length=63, verbose_name=_('Name')) author_email = fields.EmailField(blank=False, max_length=63, verbose_name=_('Email Address')) author_url = models.URLField(blank=True, max_length=127, verbose_name=_('URL')) ip_address = models.GenericIPAddressField(blank=True, null=True, protocol='both', unpack_ipv4=True, verbose_name=_('IP Address')) user_agent = fields.TextField(blank=True, verbose_name=_('User Agent')) karma = fields.IntegerField(default=0, blank=True, verbose_name=_('Karma')) status = fields.CachedForeignKey('CommentStatus', on_delete=models.PROTECT, related_name='comments', verbose_name=_('Status')) is_published = fields.BooleanField(editable=False, default=True, verbose_name=_('Is Published?')) objects = CommentManager() class Meta: abstract = True index_together = [('status', 'creation_date')] ordering = ['-creation_date'] permissions = [('can_moderate', _('Can moderate comments'))] verbose_name = _('Comment') verbose_name_plural = _('Comments') _author_email = Undefined _author_name = Undefined def __init__(self, *args, **kwargs): super(BaseComment, self).__init__(*args, **kwargs) self.old_status_id = self.status_id def __str__(self): args = ( self.pk, self.get_author_name(), ) return '#{0} {1}'.format(*args) def save(self, **kwargs): updated_fields = kwargs.get('update_fields', ()) new_record = (kwargs.get('force_insert', False) or not (self.pk or updated_fields)) if (not updated_fields and (new_record or self.status_id != self.old_status_id)): status = self.get_status() self.is_published = status.publish_comment self.old_status_id = status.id self._author_email = Undefined self._author_name = Undefined super(BaseComment, self).save(**kwargs) def get_absolute_url(self): if not hasattr(self, 'post'): msg = ('{cls} is missing a post. Define {cls}.post, or override ' '{cls}.get_absolute_url().') raise ImproperlyConfigured(msg.format(cls=self.__class__.__name__)) else: return '#'.join((self.post.get_absolute_url(), self.get_anchor())) # CUSTOM METHODS def get_anchor(self): return settings.COMMENT_ANCHOR_PATTERN.format(**self.get_anchor_data()) def get_anchor_data(self): data = { 'id': self.id, 'parent_id': self.parent_id, } if hasattr(self, 'post_id'): data['post_id'] = self.post_id elif hasattr(self, 'post'): data['post_id'] = self.post.pk return data def get_author_email(self): if self._author_email is Undefined: if not hasattr(self, 'author') or self.author is None: self._author_email = self.author_email else: self._author_email = self.author.email return self._author_email get_author_email.short_description = _('Email Address') def get_author_link(self): url = self.get_author_url() name = conditional_escape(self.get_author_name()) if url: return mark_safe('<a href="{0}" rel="nofollow">{1}</a>'.format( url, name)) else: return mark_safe(name) get_author_link.short_description = _('Author') def get_author_name(self): if self._author_name is Undefined: if not hasattr(self, 'author') or self.author is None: self._author_name = self.author_name else: self._author_name = (self.author.get_full_name() or self.author.get_username()) return self._author_name get_author_name.short_description = _('Name') def get_author_url(self): return self.author_url get_author_url.short_description = _('URL') def get_children(self, limit=None, status=None, order_by=None): qs = self.children.all() qs = qs.prefetch_related('author') if status is None: qs = qs.published() elif isinstance(status, six.string_types): qs = qs.filter(status__api_id=status) else: qs = qs.filter(status=status) if order_by is None: qs = qs.order_by('-creation_date') else: qs = qs.order_by(order_by) if limit: qs = qs[:limit] return qs def get_content(self): status = self.get_status() if status.comment_replacement: return mark_safe(status.comment_replacement) elif hasattr(self, 'content_html'): return mark_safe(self.content_html) elif hasattr(self, 'content'): return mark_safe(linebreaks(self.content, autoescape=True)) else: msg = ('{cls} is missing a content. Define {cls}.content_html, ' '{cls}.content, or override {cls}.get_content().') raise ImproperlyConfigured(msg.format(cls=self.__class__.__name__)) get_content.short_description = _('Content') def get_date(self): return self.creation_date get_date.short_description = _('Date') def get_excerpt(self, max_words=20, end_text='...'): content = self.get_content() if hasattr(content, '__html__'): # The __html__ attribute means the content was previously # marked as safe, so can include HTML tags. truncator = Truncator(content.__html__()) if end_text == '...': end_text = '…' return mark_safe(truncator.words(max_words, end_text, html=True)) else: return Truncator(content).words(max_words, end_text, html=False) get_excerpt.short_description = _('Excerpt') def get_link(self): url = self.get_absolute_url() text = ugettext('{author} on {post}').format( **{ 'author': self.get_author_name(), 'post': self.get_post(), }) return mark_safe('<a href="{0}">{1}</a>'.format(url, escape(text))) def get_status(self): return self.status get_excerpt.short_description = _('Status')