2014-05-01 16:53:02 +00:00
|
|
|
from uuid import uuid4
|
2014-03-18 15:54:01 +00:00
|
|
|
from django.db import models
|
|
|
|
from django.utils.translation import ugettext_lazy as _
|
2014-03-31 10:26:28 +00:00
|
|
|
from django.conf import settings
|
2014-04-25 10:47:26 +00:00
|
|
|
from django.db.models.signals import post_save, post_delete
|
|
|
|
from django.utils.text import slugify
|
2014-08-24 13:13:49 +00:00
|
|
|
from django.core.urlresolvers import reverse
|
2014-03-28 16:50:52 +00:00
|
|
|
from filer.fields.image import FilerImageField
|
2014-04-03 11:31:43 +00:00
|
|
|
from filer.models.foldermodels import Folder
|
2014-03-18 15:54:01 +00:00
|
|
|
|
2014-04-04 13:00:23 +00:00
|
|
|
from shelfzilla.models import Model
|
2014-03-18 15:54:01 +00:00
|
|
|
|
|
|
|
|
2014-04-04 13:00:23 +00:00
|
|
|
class Publisher(Model):
|
2014-04-22 22:34:04 +00:00
|
|
|
name = models.CharField(_('Name'), max_length=128)
|
2014-04-02 13:31:19 +00:00
|
|
|
slug = models.SlugField(_('Slug'), blank=True, null=True)
|
2014-03-28 14:48:05 +00:00
|
|
|
url = models.URLField(_('URL'), blank=True, null=True)
|
2014-03-18 15:54:01 +00:00
|
|
|
|
2014-04-02 13:31:19 +00:00
|
|
|
# Cache
|
|
|
|
_series = None
|
|
|
|
_volumes = None
|
|
|
|
|
2014-03-26 14:48:22 +00:00
|
|
|
def __unicode__(self):
|
|
|
|
return u'{}'.format(self.name)
|
|
|
|
|
2014-04-23 15:16:00 +00:00
|
|
|
def get_series_volumes(self, series):
|
|
|
|
try:
|
|
|
|
series = self.series.get(pk=series.pk)
|
|
|
|
return series.volumes.filter(publisher=self)
|
|
|
|
except Series.DoesNotExist:
|
|
|
|
return []
|
|
|
|
|
2014-08-24 13:13:49 +00:00
|
|
|
def get_absolute_url(self):
|
|
|
|
args = [self.pk]
|
|
|
|
if self.slug:
|
|
|
|
args.append(self.slug)
|
|
|
|
return reverse('publishers.detail', args=args)
|
|
|
|
|
2014-03-26 14:48:22 +00:00
|
|
|
@property
|
|
|
|
def series(self):
|
2014-04-02 13:31:19 +00:00
|
|
|
result = []
|
|
|
|
if not self._series:
|
|
|
|
queryset = self.volumes.order_by('series__id')\
|
|
|
|
.distinct('series').values_list('series')
|
|
|
|
|
2014-04-04 21:04:28 +00:00
|
|
|
result = Series.objects.filter(pk__in=queryset)
|
2014-04-02 13:31:19 +00:00
|
|
|
|
|
|
|
self._series = result
|
|
|
|
return self._series
|
2014-03-26 14:48:22 +00:00
|
|
|
|
2014-03-18 15:54:01 +00:00
|
|
|
class Meta:
|
2014-03-28 14:48:05 +00:00
|
|
|
ordering = ['name']
|
2014-03-18 15:54:01 +00:00
|
|
|
verbose_name = _('Publisher')
|
|
|
|
verbose_name_plural = _('Publishers')
|
|
|
|
|
|
|
|
|
2014-04-04 13:00:23 +00:00
|
|
|
class Series(Model):
|
2014-08-25 11:45:55 +00:00
|
|
|
SERIES_STATUS = (
|
2014-08-25 16:02:08 +00:00
|
|
|
('open', _('Open')),
|
|
|
|
('finished', _('Finished')),
|
|
|
|
('cancelled', _('Cancelled')),
|
|
|
|
('on-hold', _('On-hold'))
|
2014-08-25 11:45:55 +00:00
|
|
|
)
|
|
|
|
|
2014-04-03 17:31:37 +00:00
|
|
|
name = models.CharField(_('Name'), max_length=256)
|
2014-04-04 17:05:33 +00:00
|
|
|
slug = models.SlugField(_('Slug'), blank=True, null=True, max_length=256)
|
2014-03-28 16:50:52 +00:00
|
|
|
cover = FilerImageField(blank=True, null=True)
|
|
|
|
summary = models.TextField(_('Summary'), blank=True, null=True)
|
2014-03-28 16:57:53 +00:00
|
|
|
finished = models.BooleanField(_('Finished'), default=False)
|
2014-08-25 11:45:55 +00:00
|
|
|
status = models.CharField(_('Status'), choices=SERIES_STATUS,
|
|
|
|
default='open', max_length=16)
|
2014-03-28 16:50:52 +00:00
|
|
|
|
2014-04-04 13:00:23 +00:00
|
|
|
original_publisher = models.ForeignKey(
|
2014-08-25 18:58:25 +00:00
|
|
|
Publisher, related_name='original_series', null=True, blank=True)
|
2014-04-04 13:00:23 +00:00
|
|
|
|
2014-04-22 22:34:04 +00:00
|
|
|
art = models.ManyToManyField(
|
2014-08-25 18:58:25 +00:00
|
|
|
'Person', related_name='artist_of', null=True, blank=True)
|
2014-04-22 22:34:04 +00:00
|
|
|
story = models.ManyToManyField(
|
2014-08-25 18:58:25 +00:00
|
|
|
'Person', related_name='scriptwriter_of', null=True, blank=True)
|
2014-04-04 13:00:23 +00:00
|
|
|
|
2014-04-03 11:31:43 +00:00
|
|
|
folder = models.ForeignKey(Folder, null=True, blank=True)
|
|
|
|
|
2014-03-28 16:50:52 +00:00
|
|
|
# Cache
|
2014-08-25 13:17:29 +00:00
|
|
|
_languages = None
|
2014-03-18 15:54:01 +00:00
|
|
|
|
2014-03-26 14:48:22 +00:00
|
|
|
def __unicode__(self):
|
|
|
|
return u'{}'.format(self.name)
|
|
|
|
|
2014-04-25 11:19:34 +00:00
|
|
|
def have_review_pending(self):
|
|
|
|
if self.for_review:
|
|
|
|
return True
|
|
|
|
|
|
|
|
for vol in self.volumes.all():
|
|
|
|
if vol.for_review:
|
|
|
|
return True
|
|
|
|
|
|
|
|
return False
|
|
|
|
|
2014-08-24 13:13:49 +00:00
|
|
|
def get_absolute_url(self):
|
|
|
|
args = [self.pk]
|
|
|
|
if self.slug:
|
|
|
|
args.append(self.slug)
|
|
|
|
return reverse('series.detail', args=args)
|
|
|
|
|
2014-08-25 11:45:55 +00:00
|
|
|
def get_status_display_class(self):
|
|
|
|
pairs = {
|
2014-08-25 13:17:29 +00:00
|
|
|
'open': 'success', 'finished': 'success', 'cancelled': 'danger',
|
|
|
|
'on-hold': 'warning'
|
2014-08-25 11:45:55 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return pairs[self.status]
|
|
|
|
|
2014-04-23 15:16:00 +00:00
|
|
|
@property
|
|
|
|
def volumes_by_publisher(self):
|
2014-08-25 13:17:29 +00:00
|
|
|
return self.volumes.order_by('publisher__name', 'language', 'number')
|
2014-04-23 15:16:00 +00:00
|
|
|
|
|
|
|
@property
|
|
|
|
def last_volume_cover(self):
|
|
|
|
return self.volumes.filter(cover__isnull=False).last().cover
|
|
|
|
|
2014-08-25 13:17:29 +00:00
|
|
|
def languages(self):
|
|
|
|
if not self._languages:
|
2014-03-28 16:50:52 +00:00
|
|
|
result = []
|
2014-08-25 13:17:29 +00:00
|
|
|
queryset = self.volumes.order_by('language__id')\
|
|
|
|
.distinct('language').values_list('language')
|
|
|
|
result = Language.objects.filter(pk__in=queryset)
|
2014-03-28 16:50:52 +00:00
|
|
|
|
2014-08-25 13:17:29 +00:00
|
|
|
self._languages = result
|
2014-03-28 16:50:52 +00:00
|
|
|
|
2014-08-25 13:17:29 +00:00
|
|
|
return self._languages
|
2014-03-26 14:48:22 +00:00
|
|
|
|
2014-03-18 15:54:01 +00:00
|
|
|
class Meta:
|
2014-03-28 14:48:05 +00:00
|
|
|
ordering = ['name']
|
2014-03-18 15:54:01 +00:00
|
|
|
verbose_name = _('Series')
|
|
|
|
verbose_name_plural = _('Series')
|
|
|
|
|
|
|
|
|
2014-08-25 13:17:29 +00:00
|
|
|
class SeriesSummary(Model):
|
|
|
|
series = models.ForeignKey('Series', related_name='summaries')
|
|
|
|
language = models.ForeignKey('Language')
|
|
|
|
summary = models.TextField(_('Summary'), null=True, blank=True)
|
|
|
|
|
|
|
|
class Meta:
|
|
|
|
unique_together = ('series', 'language', )
|
|
|
|
|
|
|
|
|
|
|
|
class SeriesPublisher(Model):
|
|
|
|
series = models.ForeignKey('Series', related_name='publishers')
|
|
|
|
publisher = models.ForeignKey('Publisher', related_name='series_published')
|
|
|
|
status = models.CharField(_('Status'), choices=Series.SERIES_STATUS,
|
|
|
|
default='open', max_length=16)
|
|
|
|
actual_publisher = models.BooleanField(_('Current publisher'),
|
|
|
|
default=True)
|
|
|
|
|
|
|
|
def get_status_display_class(self):
|
|
|
|
pairs = {
|
|
|
|
'open': 'success', 'finished': 'success', 'cancelled': 'danger',
|
|
|
|
'on-hold': 'warning'
|
|
|
|
}
|
|
|
|
|
|
|
|
return pairs[self.status]
|
|
|
|
|
|
|
|
@property
|
|
|
|
def volumes(self):
|
|
|
|
return self.series.volumes.filter(publisher=self.publisher)
|
|
|
|
|
|
|
|
|
2014-04-04 13:00:23 +00:00
|
|
|
class Volume(Model):
|
2014-08-25 18:58:25 +00:00
|
|
|
collection = models.ForeignKey('VolumeCollection', null=True,
|
|
|
|
related_name='volumes')
|
2014-04-25 10:47:26 +00:00
|
|
|
number = models.IntegerField(_('Number'), null=True, blank=True)
|
|
|
|
name = models.CharField(_('Name'), max_length=64, null=True, blank=True)
|
2014-03-26 14:48:22 +00:00
|
|
|
series = models.ForeignKey(Series, related_name="volumes")
|
|
|
|
publisher = models.ForeignKey(Publisher, related_name="volumes")
|
2014-03-28 16:50:52 +00:00
|
|
|
cover = FilerImageField(null=True, blank=True)
|
2014-03-18 15:54:01 +00:00
|
|
|
isbn_10 = models.CharField(
|
|
|
|
_('ISBN-10'), max_length=10, blank=True, null=True)
|
|
|
|
isbn_13 = models.CharField(
|
|
|
|
_('ISBN-13'), max_length=13, blank=True, null=True)
|
|
|
|
|
2014-08-25 11:45:55 +00:00
|
|
|
language = models.ForeignKey('Language', null=True)
|
|
|
|
|
2014-04-04 13:00:23 +00:00
|
|
|
retail_price = models.DecimalField(
|
2014-04-25 10:47:26 +00:00
|
|
|
_('Retail price'), max_digits=5, decimal_places=2,
|
|
|
|
null=True, blank=True)
|
|
|
|
pages = models.IntegerField(_('Pages'), null=True, blank=True)
|
2014-04-04 13:00:23 +00:00
|
|
|
release_date = models.DateField(_('Release date'), null=True)
|
|
|
|
|
2014-03-26 14:48:22 +00:00
|
|
|
def __unicode__(self):
|
2014-04-25 16:12:50 +00:00
|
|
|
if self.name:
|
|
|
|
return u'{} {}'.format(self.series.name, self.name)
|
2014-08-24 15:05:32 +00:00
|
|
|
elif self.number:
|
2014-04-25 16:12:50 +00:00
|
|
|
return u'{} #{}'.format(self.series.name, self.number)
|
2014-08-24 15:05:32 +00:00
|
|
|
else:
|
|
|
|
return u'{}'.format(self.series.name)
|
2014-03-26 14:48:22 +00:00
|
|
|
|
2014-03-18 15:54:01 +00:00
|
|
|
class Meta:
|
2014-08-25 13:17:29 +00:00
|
|
|
ordering = ['series__name', 'language', 'number']
|
2014-03-18 15:54:01 +00:00
|
|
|
verbose_name = _('Volume')
|
|
|
|
verbose_name_plural = _('Volumes')
|
2014-03-31 10:26:28 +00:00
|
|
|
|
|
|
|
|
2014-08-25 18:58:25 +00:00
|
|
|
class VolumeCollection(Model):
|
|
|
|
name = models.CharField(_('Name'), max_length=32)
|
|
|
|
series = models.ForeignKey('Series', related_name='collections')
|
|
|
|
default = models.BooleanField(_('Default'), default=True)
|
|
|
|
|
|
|
|
def __unicode__(self):
|
2014-08-25 21:11:47 +00:00
|
|
|
return u'{} - {}'.format(self.series, self.name)
|
2014-08-25 18:58:25 +00:00
|
|
|
|
|
|
|
class Meta:
|
|
|
|
ordering = ['name', ]
|
|
|
|
verbose_name = _('Collection')
|
|
|
|
verbose_name_plural = _('Collections')
|
|
|
|
|
|
|
|
|
2014-04-04 13:00:23 +00:00
|
|
|
class Person(Model):
|
|
|
|
name = models.CharField(_('Name'), max_length=256)
|
|
|
|
slug = models.SlugField(_('Slug'), blank=True, null=True)
|
|
|
|
|
2014-04-22 22:34:04 +00:00
|
|
|
def __unicode__(self):
|
|
|
|
return u'{}'.format(self.name)
|
|
|
|
|
2014-04-04 13:00:23 +00:00
|
|
|
class Meta:
|
|
|
|
ordering = ['name']
|
|
|
|
verbose_name = _('Person')
|
|
|
|
verbose_name_plural = _('Persons')
|
|
|
|
|
|
|
|
|
2014-08-25 11:45:55 +00:00
|
|
|
class Language(models.Model):
|
|
|
|
name = models.CharField(_('Name'), max_length=32)
|
|
|
|
code = models.CharField(_('Code'), max_length=5)
|
|
|
|
|
|
|
|
def __unicode__(self):
|
|
|
|
return self.name
|
|
|
|
|
|
|
|
class Meta:
|
|
|
|
ordering = ['name']
|
|
|
|
verbose_name = _('Language')
|
|
|
|
verbose_name_plural = _('Languages')
|
|
|
|
|
2014-08-25 18:58:25 +00:00
|
|
|
|
2014-04-04 13:00:23 +00:00
|
|
|
#
|
|
|
|
# RELATIONS
|
|
|
|
#
|
2014-03-31 10:26:28 +00:00
|
|
|
class UserHaveVolume(models.Model):
|
|
|
|
user = models.ForeignKey(settings.AUTH_USER_MODEL,
|
|
|
|
related_name='have_volumes')
|
|
|
|
volume = models.ForeignKey(Volume, related_name='owned_by')
|
2014-06-20 08:43:13 +00:00
|
|
|
date = models.DateTimeField(_('Date'), auto_now_add=True)
|
2014-03-31 10:26:28 +00:00
|
|
|
|
2014-09-08 23:18:24 +00:00
|
|
|
_timeline_message = _('%(volume)s added to collection')
|
|
|
|
_event_type = 'have'
|
|
|
|
|
|
|
|
@property
|
|
|
|
def event_type(self):
|
|
|
|
return self._event_type
|
|
|
|
|
|
|
|
@property
|
|
|
|
def timeline_message(self):
|
|
|
|
return self._timeline_message % {'volume': self.volume}
|
|
|
|
|
2014-03-31 10:26:28 +00:00
|
|
|
def __unicode__(self):
|
2015-01-31 12:46:39 +00:00
|
|
|
return u"{} {} {}".format(
|
|
|
|
self.user.username,
|
2014-03-31 10:26:28 +00:00
|
|
|
_('have'),
|
2015-01-31 12:46:39 +00:00
|
|
|
self.volume
|
2014-03-31 10:26:28 +00:00
|
|
|
)
|
|
|
|
|
2014-05-27 19:26:53 +00:00
|
|
|
class Meta:
|
|
|
|
ordering = ('volume__series__name', 'volume__number', )
|
|
|
|
|
2014-03-31 10:26:28 +00:00
|
|
|
|
|
|
|
class UserWishlistVolume(models.Model):
|
|
|
|
user = models.ForeignKey(settings.AUTH_USER_MODEL,
|
2014-03-31 15:54:30 +00:00
|
|
|
related_name='wishlisted_volumes')
|
2014-03-31 10:26:28 +00:00
|
|
|
volume = models.ForeignKey(Volume, related_name='wishlisted_by')
|
2014-06-20 08:43:13 +00:00
|
|
|
date = models.DateTimeField(_('Date'), auto_now_add=True)
|
2014-03-31 10:26:28 +00:00
|
|
|
|
2014-09-08 23:18:24 +00:00
|
|
|
_timeline_message = _('%(volume)s wishlisted')
|
|
|
|
_event_type = 'wishlist'
|
|
|
|
|
|
|
|
@property
|
|
|
|
def event_type(self):
|
|
|
|
return self._event_type
|
|
|
|
|
|
|
|
@property
|
|
|
|
def timeline_message(self):
|
|
|
|
return self._timeline_message % {'volume': self.volume}
|
|
|
|
|
2014-03-31 10:26:28 +00:00
|
|
|
def __unicode__(self):
|
2015-01-31 12:46:39 +00:00
|
|
|
return u"{} {} {}".format(
|
|
|
|
self.user.username,
|
2014-03-31 10:26:28 +00:00
|
|
|
_('wants'),
|
2015-01-31 12:46:39 +00:00
|
|
|
self.volume
|
2014-03-31 15:54:30 +00:00
|
|
|
)
|
2014-04-03 11:31:43 +00:00
|
|
|
|
2014-05-27 19:26:53 +00:00
|
|
|
class Meta:
|
|
|
|
ordering = ('volume__series__name', 'volume__number', )
|
|
|
|
|
2014-09-08 15:50:03 +00:00
|
|
|
|
|
|
|
class UserReadVolume(models.Model):
|
|
|
|
user = models.ForeignKey(settings.AUTH_USER_MODEL,
|
|
|
|
related_name='read_volumes')
|
|
|
|
volume = models.ForeignKey(Volume, related_name='read_by')
|
|
|
|
date = models.DateTimeField(_('Date'), auto_now_add=True)
|
|
|
|
|
2014-09-08 23:18:24 +00:00
|
|
|
_timeline_message = _('%(volume)s marked as read')
|
|
|
|
_event_type = 'read'
|
|
|
|
|
|
|
|
@property
|
|
|
|
def event_type(self):
|
|
|
|
return self._event_type
|
|
|
|
|
|
|
|
@property
|
|
|
|
def timeline_message(self):
|
|
|
|
return self._timeline_message % {'volume': self.volume}
|
|
|
|
|
2014-09-08 15:50:03 +00:00
|
|
|
def __unicode__(self):
|
2015-01-31 12:46:39 +00:00
|
|
|
return u"{} {} {}".format(
|
|
|
|
self.user.username,
|
2014-09-08 15:50:03 +00:00
|
|
|
_('have read'),
|
2015-01-31 12:46:39 +00:00
|
|
|
self.volume
|
2014-09-08 15:50:03 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
class Meta:
|
|
|
|
ordering = ('volume__series__name', 'volume__number', )
|
|
|
|
|
|
|
|
|
2014-04-04 13:00:23 +00:00
|
|
|
#
|
|
|
|
# SIGNALS
|
|
|
|
#
|
2014-04-03 11:31:43 +00:00
|
|
|
def series_check_filer(sender, instance, created, **kwargs):
|
|
|
|
name = instance.name
|
|
|
|
|
|
|
|
# Check folder
|
2014-08-25 18:58:25 +00:00
|
|
|
# Fix for loaddata import
|
|
|
|
if instance.folder_id:
|
|
|
|
try:
|
|
|
|
Folder.objects.get(pk=instance.folder_id)
|
|
|
|
except Folder.DoesNotExist:
|
|
|
|
instance.folder_id = None
|
|
|
|
instance.folder = None
|
|
|
|
|
2014-04-03 11:31:43 +00:00
|
|
|
if not instance.folder:
|
2014-04-03 17:31:37 +00:00
|
|
|
folder, is_new = Folder.objects.get_or_create(
|
2014-04-03 11:31:43 +00:00
|
|
|
name=name,
|
|
|
|
parent_id=settings.COVER_FOLDER_PK,
|
|
|
|
owner_id=settings.COVER_FOLDER_OWNER_PK,
|
|
|
|
)
|
|
|
|
instance.folder = folder
|
|
|
|
instance.save()
|
|
|
|
else:
|
|
|
|
# Check for name change
|
|
|
|
if instance.folder.name != name:
|
|
|
|
instance.folder.name = name
|
|
|
|
instance.folder.save()
|
|
|
|
|
|
|
|
# Check file name
|
|
|
|
if instance.cover:
|
|
|
|
if instance.cover.name != 'Cover':
|
|
|
|
instance.cover.name = 'Cover'
|
|
|
|
instance.cover.save()
|
|
|
|
|
|
|
|
if instance.cover.folder != instance.folder:
|
|
|
|
instance.cover.folder = instance.folder
|
|
|
|
instance.cover.save()
|
|
|
|
|
|
|
|
|
|
|
|
def volume_check_filer(sender, instance, created, **kwargs):
|
|
|
|
if instance.cover:
|
|
|
|
# Check cover to be in series folder
|
|
|
|
if instance.cover.folder != instance.series.folder:
|
|
|
|
instance.cover.folder = instance.series.folder
|
|
|
|
instance.cover.save()
|
|
|
|
|
|
|
|
# Check filename
|
2014-05-01 16:53:02 +00:00
|
|
|
cover_name = uuid4()
|
2014-04-25 10:47:26 +00:00
|
|
|
if instance.name:
|
|
|
|
cover_name = slugify(instance.name)
|
|
|
|
elif instance.number:
|
|
|
|
cover_name = '{:03}'.format(instance.number)
|
2014-04-03 11:31:43 +00:00
|
|
|
if instance.cover.name != cover_name:
|
|
|
|
instance.cover.name = cover_name
|
|
|
|
instance.cover.save()
|
|
|
|
|
|
|
|
|
2014-04-25 10:47:26 +00:00
|
|
|
def series_delete_folder(sender, instance, using, **kwargs):
|
2015-01-31 13:41:28 +00:00
|
|
|
try:
|
|
|
|
if instance.folder:
|
|
|
|
instance.folder.delete()
|
|
|
|
except:
|
|
|
|
pass
|
2014-04-25 10:47:26 +00:00
|
|
|
|
|
|
|
|
|
|
|
def volume_delete_cover(sender, instance, **kwargs):
|
2014-08-24 10:23:59 +00:00
|
|
|
try:
|
|
|
|
if instance.cover:
|
|
|
|
instance.cover.delete()
|
|
|
|
except:
|
|
|
|
pass
|
2014-04-25 10:47:26 +00:00
|
|
|
|
2014-04-03 11:31:43 +00:00
|
|
|
post_save.connect(series_check_filer, sender=Series)
|
2014-04-03 17:31:37 +00:00
|
|
|
post_save.connect(volume_check_filer, sender=Volume)
|
2014-04-25 10:47:26 +00:00
|
|
|
post_delete.connect(series_delete_folder, sender=Series)
|
|
|
|
post_delete.connect(volume_delete_cover, sender=Volume)
|