from django.db import models
from django.contrib.auth.models import User
from django.conf import settings
from django.db.models.signals import post_save
from django.dispatch import receiver
from django.utils.functional import cached_property
from tld import get_tld
from django.core.urlresolvers import reverse
from taggit.managers import TaggableManager
from taggit.models import TagBase, ItemBase
from readme.download import download, DownloadException
from readme.scrapers import parse
import logging
import bleach
request_log = logging.getLogger('readme.requests')
class ItemQuerySet(models.query.QuerySet):
def tagged(self, *tags):
filtered = self
for tag in tags:
filtered = filtered.filter(tags__name=tag)
return filtered
def without(self, *tags):
filtered = self
for tag in tags:
filtered = filtered.exclude(tags__name=tag)
return filtered
class ItemManager(models.Manager):
def get_queryset(self):
return ItemQuerySet(self.model)
def __getattr__(self, name, *args):
if name.startswith("_"):
raise AttributeError
return getattr(self.get_query_set(), name, *args)
[docs]class ItemTag(TagBase):
def slugify(self, tag, i=None):
return tag
[docs]class TaggedItem(ItemBase):
tag = models.ForeignKey(ItemTag, related_name="%(app_label)s_%(class)s_items")
content_object = models.ForeignKey('Item')
@classmethod
def tags_for(cls, model, instance=None):
if instance is not None:
return cls.tag_model().objects.filter(**{
'%s__content_object' % cls.tag_relname(): instance
})
return cls.tag_model().objects.filter(**{
'%s__content_object__isnull' % cls.tag_relname(): False
}).distinct()
[docs]class Item(models.Model):
"""
Entry in the read-it-later-list
"""
#:param url Page url
url = models.URLField(max_length=2000)
#:param title Page title
title = models.TextField(blank=True)
#:param created Creating date of the item
created = models.DateTimeField(auto_now_add=True)
#:param owner Owning user
owner = models.ForeignKey(User)
#:param readable_article Processed content of the url
readable_article = models.TextField(blank=True)
#:param safe_article Escaped and stripped of tags
safe_article = models.TextField(blank=True)
#:param tags User assigned tags
tags = TaggableManager(blank=True, through=TaggedItem)
objects = ItemManager()
@property
def created_as_str(self):
created = self.created.isoformat().split('+')[0]
return created + 'Z'
@cached_property
def domain(self):
"""
Domain of the url
"""
return get_tld(self.url, fail_silently=True)
def get_absolute_url(self):
return reverse('item_view', args=[str(self.id)])
def get_api_url(self):
return '/api/items/{}/'.format(str(self.id))
def get_update_url(self):
return reverse('item_update', args=[str(self.id)])
def get_tag_names(self):
return sorted(self.tags.values_list('name', flat=True))
@property
def tag_names(self):
try:
return self.get_tag_names()
except ValueError:
return self._tags_to_save
@tag_names.setter
def tag_names(self, names):
if self.tag_names or names:
self.tags.set(*names)
else:
self._tags_to_save = names
def get_safe_article(self):
if self.readable_article:
return bleach.clean(self.readable_article, strip=True, tags=[])
else:
return ''
def save(self, *args, **kwargs):
self.safe_article = self.get_safe_article()
super(Item, self).save(*args, **kwargs)
[docs] def fetch_article(self):
"""
Fetches a title and a readable_article for the current url.
It uses the scrapers module for this and only downloads the content.
"""
try:
dl = download(self.url, max_content_length=settings.PYPO_MAX_CONTENT_LENGTH)
except DownloadException:
# TODO show a message that the download failed?
self.title = self.url
self.readable_article = None
else:
self.title, self.readable_article = parse(self, content_type=dl.content_type,
text=dl.text, content=dl.content)
[docs]class UserProfile(models.Model):
user = models.OneToOneField(User, primary_key=True)
theme = models.CharField('Custom Theme', max_length=30, default=settings.PYPO_DEFAULT_THEME)
can_invite = models.BooleanField(default=True)
new_window = models.BooleanField('Open links in a new page', default=False)
items_per_page = models.PositiveIntegerField('Number of items shown on a page', default=50)
excluded_tags = TaggableManager('Tags that can be excluded', blank=True)
show_excluded = models.BooleanField('Show excluded items anyway', default=False)
@property
def id(self):
"""
Just to tell the TaggableManager what the primary key of this model is
"""
return self.user.id
@receiver(post_save, sender=User)
def create_profile_for_new_user(sender, created, instance, **kwargs):
if created:
profile = UserProfile(user=instance)
profile.save()