diff --git a/.gitignore b/.gitignore index 029e271..ec8a5a7 100644 --- a/.gitignore +++ b/.gitignore @@ -15,4 +15,6 @@ celerybeat-schedule.db .Spotlight-V100 .Trashes ehthumbs.db -Thumbs.db \ No newline at end of file +Thumbs.db + +*~ diff --git a/comics/migrations/0005_issue_number.py b/comics/migrations/0005_issue_number.py new file mode 100644 index 0000000..efae287 --- /dev/null +++ b/comics/migrations/0005_issue_number.py @@ -0,0 +1,42 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.1 on 2017-11-07 21:42 +from __future__ import unicode_literals + +from django.db import migrations, models + +from ..utils.comicapi.issuestring import IssueString + + +def add_issue_number_data(apps, schema_editor): + + Issue = apps.get_model('comics', 'Issue') + query = Issue.objects.all() + for issue in query: + new_number = IssueString(issue.number).asString(pad=3) + issue.number = new_number + issue.save() + + +def remove_issue_number_data(apps, schema_editor): + + Issue = apps.get_model('comics', 'Issue') + Issue.objects.update(number='') + + +class Migration(migrations.Migration): + + dependencies = [ + ('comics', '0004_issue_page_count'), + ] + + operations = [ + migrations.AlterField( + model_name='issue', + name='number', + field=models.CharField(max_length=25, verbose_name='Issue number'), + ), + migrations.RunPython( + add_issue_number_data, + reverse_code=remove_issue_number_data + ), + ] diff --git a/comics/migrations/0006_publisher_slug.py b/comics/migrations/0006_publisher_slug.py new file mode 100644 index 0000000..2027694 --- /dev/null +++ b/comics/migrations/0006_publisher_slug.py @@ -0,0 +1,51 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.1 on 2017-11-14 12:22 +from __future__ import unicode_literals + +from django.db import migrations, models +from django.utils.text import slugify + +SLUG_LENGTH = 200 + + +def add_slug_data(apps, schema_editor): + Publisher = apps.get_model( + 'comics', 'Publisher') + query = Publisher.objects.all() + for publisher in query: + new_slug = slugify(publisher.name) + publisher.slug = new_slug + publisher.save() + + +def remove_slug_data(apps, schema_editor): + Publisher = apps.get_model( + 'comics', 'Publisher') + Publisher.objects.update(slug='') + + +class Migration(migrations.Migration): + + dependencies = [ + ('comics', '0005_issue_number'), + ] + + operations = [ + migrations.AddField( + model_name='publisher', + name='slug', + field=models.SlugField( + max_length=SLUG_LENGTH, + default=''), + ), + migrations.RunPython( + add_slug_data, + reverse_code=remove_slug_data + ), + migrations.AlterField( + model_name='publisher', + name='slug', + field=models.SlugField( + max_length=SLUG_LENGTH), + ), + ] diff --git a/comics/migrations/0007_series_slug.py b/comics/migrations/0007_series_slug.py new file mode 100644 index 0000000..a6477bb --- /dev/null +++ b/comics/migrations/0007_series_slug.py @@ -0,0 +1,55 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.1 on 2017-11-14 14:29 +from __future__ import unicode_literals + +from django.db import migrations, models +from django.utils.text import slugify + +SLUG_LENGTH = 200 + + +def add_slug_data(apps, schema_editor): + Series = apps.get_model( + 'comics', 'Series') + query = Series.objects.all() + for series in query: + if series.year is not None: + n = (series.name + ' ' + str(series.year)) + else: + n = series.name + new_slug = slugify(n) + series.slug = new_slug + series.save() + + +def remove_slug_data(apps, schema_editor): + Series = apps.get_model( + 'comics', 'Series') + Series.objects.update(slug='') + + +class Migration(migrations.Migration): + + dependencies = [ + ('comics', '0006_publisher_slug'), + ] + + operations = [ + migrations.AddField( + model_name='series', + name='slug', + field=models.SlugField( + max_length=SLUG_LENGTH, + default=''), + ), + migrations.RunPython( + add_slug_data, + reverse_code=remove_slug_data + ), + migrations.AlterField( + model_name='series', + name='slug', + field=models.SlugField( + max_length=SLUG_LENGTH), + ), + ] diff --git a/comics/migrations/0008_creator_slug.py b/comics/migrations/0008_creator_slug.py new file mode 100644 index 0000000..61695b3 --- /dev/null +++ b/comics/migrations/0008_creator_slug.py @@ -0,0 +1,51 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.1 on 2017-11-14 18:31 +from __future__ import unicode_literals + +from django.db import migrations, models +from django.utils.text import slugify + +SLUG_LENGTH = 200 + + +def add_slug_data(apps, schema_editor): + Creator = apps.get_model( + 'comics', 'Creator') + query = Creator.objects.all() + for creator in query: + new_slug = slugify(creator.name) + creator.slug = new_slug + creator.save() + + +def remove_slug_data(apps, schema_editor): + Creator = apps.get_model( + 'comics', 'Creator') + Creator.objects.update(slug='') + + +class Migration(migrations.Migration): + + dependencies = [ + ('comics', '0007_series_slug'), + ] + + operations = [ + migrations.AddField( + model_name='creator', + name='slug', + field=models.SlugField( + max_length=SLUG_LENGTH, + default=''), + ), + migrations.RunPython( + add_slug_data, + reverse_code=remove_slug_data + ), + migrations.AlterField( + model_name='creator', + name='slug', + field=models.SlugField( + max_length=SLUG_LENGTH), + ), + ] diff --git a/comics/migrations/0009_team_slug.py b/comics/migrations/0009_team_slug.py new file mode 100644 index 0000000..c762cba --- /dev/null +++ b/comics/migrations/0009_team_slug.py @@ -0,0 +1,49 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.1 on 2017-11-14 19:23 +from __future__ import unicode_literals + +from django.db import migrations, models +from django.utils.text import slugify + +SLUG_LENGTH = 200 + + +def add_slug_data(apps, schema_editor): + Team = apps.get_model('comics', 'Team') + query = Team.objects.all() + for team in query: + new_slug = slugify(team.name) + team.slug = new_slug + team.save() + + +def remove_slug_data(apps, schema_editor): + Team = apps.get_model('comics', 'Team') + Team.objects.update(slug='') + + +class Migration(migrations.Migration): + + dependencies = [ + ('comics', '0008_creator_slug'), + ] + + operations = [ + migrations.AddField( + model_name='team', + name='slug', + field=models.SlugField( + max_length=SLUG_LENGTH, + default=''), + ), + migrations.RunPython( + add_slug_data, + reverse_code=remove_slug_data + ), + migrations.AlterField( + model_name='team', + name='slug', + field=models.SlugField( + max_length=SLUG_LENGTH), + ), + ] diff --git a/comics/migrations/0010_character_slug.py b/comics/migrations/0010_character_slug.py new file mode 100644 index 0000000..4c7c472 --- /dev/null +++ b/comics/migrations/0010_character_slug.py @@ -0,0 +1,49 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.1 on 2017-11-14 19:38 +from __future__ import unicode_literals + +from django.db import migrations, models +from django.utils.text import slugify + +SLUG_LENGTH = 200 + + +def add_slug_data(apps, schema_editor): + Character = apps.get_model('comics', 'Character') + query = Character.objects.all() + for character in query: + new_slug = slugify(character.name) + character.slug = new_slug + character.save() + + +def remove_slug_data(apps, schema_editor): + Character = apps.get_model('comics', 'Character') + Character.objects.update(slug='') + + +class Migration(migrations.Migration): + + dependencies = [ + ('comics', '0009_team_slug'), + ] + + operations = [ + migrations.AddField( + model_name='character', + name='slug', + field=models.SlugField( + max_length=SLUG_LENGTH, + default=''), + ), + migrations.RunPython( + add_slug_data, + reverse_code=remove_slug_data + ), + migrations.AlterField( + model_name='character', + name='slug', + field=models.SlugField( + max_length=SLUG_LENGTH), + ), + ] diff --git a/comics/migrations/0011_storyarc_slug.py b/comics/migrations/0011_storyarc_slug.py new file mode 100644 index 0000000..33c59ca --- /dev/null +++ b/comics/migrations/0011_storyarc_slug.py @@ -0,0 +1,49 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.1 on 2017-11-14 20:00 +from __future__ import unicode_literals + +from django.db import migrations, models +from django.utils.text import slugify + +SLUG_LENGTH = 200 + + +def add_slug_data(apps, schema_editor): + Arc = apps.get_model('comics', 'Arc') + query = Arc.objects.all() + for arc in query: + new_slug = slugify(arc.name) + arc.slug = new_slug + arc.save() + + +def remove_slug_data(apps, schema_editor): + Arc = apps.get_model('comics', 'Arc') + Arc.objects.update(slug='') + + +class Migration(migrations.Migration): + + dependencies = [ + ('comics', '0010_character_slug'), + ] + + operations = [ + migrations.AddField( + model_name='arc', + name='slug', + field=models.SlugField( + max_length=SLUG_LENGTH, + default=''), + ), + migrations.RunPython( + add_slug_data, + reverse_code=remove_slug_data + ), + migrations.AlterField( + model_name='arc', + name='slug', + field=models.SlugField( + max_length=SLUG_LENGTH), + ), + ] diff --git a/comics/migrations/0012_issue_slug.py b/comics/migrations/0012_issue_slug.py new file mode 100644 index 0000000..d63d054 --- /dev/null +++ b/comics/migrations/0012_issue_slug.py @@ -0,0 +1,55 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.1 on 2017-11-14 20:20 +from __future__ import unicode_literals + +from django.db import migrations, models +from django.utils.text import slugify + +SLUG_LENGTH = 200 + + +def add_slug_data(apps, schema_editor): + Issue = apps.get_model('comics', 'Issue') + query = Issue.objects.all() + for issue in query: + if issue.date is not None: + slugy = issue.series + ' ' + \ + issue.number + ' ' + str(issue.date.year) + else: + slugy = issue.series + ' ' + issue.number + + new_slug = slugify(slugy) + issue.slug = new_slug + issue.save() + + +def remove_slug_data(apps, schema_editor): + Issue = apps.get_model('comics', 'Issue') + Issue.objects.update(slug='') + + +class Migration(migrations.Migration): + + dependencies = [ + ('comics', '0011_storyarc_slug'), + ] + + operations = [ + migrations.AddField( + model_name='issue', + name='slug', + field=models.SlugField( + max_length=SLUG_LENGTH, + default=''), + ), + migrations.RunPython( + add_slug_data, + reverse_code=remove_slug_data + ), + migrations.AlterField( + model_name='issue', + name='slug', + field=models.SlugField( + max_length=SLUG_LENGTH), + ), + ] diff --git a/comics/models.py b/comics/models.py index ee67da5..275942d 100644 --- a/comics/models.py +++ b/comics/models.py @@ -1,6 +1,10 @@ from __future__ import unicode_literals -import datetime,os,rarfile,zipfile,tarfile +import datetime +import os +import rarfile +import zipfile +import tarfile from shutil import copyfile from multiselectfield import MultiSelectField @@ -17,156 +21,183 @@ Choices for model use. """ # Generated years -YEAR_CHOICES = [(r,r) for r in range(1837, datetime.date.today().year+1)] +YEAR_CHOICES = [(r, r) for r in range(1837, datetime.date.today().year + 1)] # Comic read status STATUS_CHOICES = ( - (0, 'Unread'), - (1, 'Partially read'), - (2, 'Completed'), + (0, 'Unread'), + (1, 'Partially read'), + (2, 'Completed'), ) # Creator roles for an issue ROLE_CHOICES = ( - ('artist', 'Artist'), - ('colorist', 'Colorist'), - ('cover', 'Cover'), - ('editor', 'Editor'), - ('inker', 'Inker'), - ('journalist', 'Journalist'), - ('letterer', 'Letterer'), - ('other', 'Other'), - ('penciler', 'Penciler'), - ('production', 'Production'), - ('writer', 'Writer'), + ('artist', 'Artist'), + ('colorist', 'Colorist'), + ('cover', 'Cover'), + ('editor', 'Editor'), + ('inker', 'Inker'), + ('journalist', 'Journalist'), + ('letterer', 'Letterer'), + ('other', 'Other'), + ('penciler', 'Penciler'), + ('production', 'Production'), + ('writer', 'Writer'), ) + class Settings(SingletonModel): - api_key = models.CharField( - 'ComicVine API Key', - help_text="A 40-character key provided by ComicVine. This is used to retrieve metadata about your comics. You can create a ComicVine API Key at ComicVine's API Page (ComicVine account is required).", - validators=[RegexValidator( - regex='^.{40}$', - message='Length must be 40 characters.', - code='nomatch' - )], - max_length=40, - blank=True - ) - - def __str__(self): - return "Settings" + api_key = models.CharField( + 'ComicVine API Key', + help_text="A 40-character key provided by ComicVine. This is used to retrieve metadata about your comics. You can create a ComicVine API Key at ComicVine's API Page (ComicVine account is required).", + validators=[RegexValidator( + regex='^.{40}$', + message='Length must be 40 characters.', + code='nomatch' + )], + max_length=40, + blank=True + ) + + def __str__(self): + return "Settings" + class Arc(models.Model): - cvid = models.CharField(max_length=15) - cvurl = models.URLField(max_length=200) - name = models.CharField('Arc name', max_length=200) - desc = models.TextField('Description', max_length=500, blank=True) - image = models.FilePathField('Image file path', path="media/images", blank=True) + cvid = models.CharField(max_length=15) + cvurl = models.URLField(max_length=200) + name = models.CharField('Arc name', max_length=200) + slug = models.SlugField(max_length=200, unique=True) + desc = models.TextField('Description', max_length=500, blank=True) + image = models.FilePathField( + 'Image file path', path="media/images", blank=True) + + def __str__(self): + return self.name - def __str__(self): - return self.name class Team(models.Model): - cvid = models.CharField(max_length=15) - cvurl = models.URLField(max_length=200) - name = models.CharField('Team name', max_length=200) - desc = models.TextField('Description', max_length=500, blank=True) - image = models.FilePathField('Image file path', path="media/images", blank=True) + cvid = models.CharField(max_length=15) + cvurl = models.URLField(max_length=200) + name = models.CharField('Team name', max_length=200) + slug = models.SlugField(max_length=200, unique=True) + desc = models.TextField('Description', max_length=500, blank=True) + image = models.FilePathField( + 'Image file path', path="media/images", blank=True) + + def __str__(self): + return self.name - def __str__(self): - return self.name class Character(models.Model): - cvid = models.CharField(max_length=15) - cvurl = models.URLField(max_length=200) - name = models.CharField('Character name', max_length=200) - desc = models.TextField('Description', max_length=500, blank=True) - teams = models.ManyToManyField(Team, blank=True) - image = models.FilePathField('Image file path', path="media/images", blank=True) + cvid = models.CharField(max_length=15) + cvurl = models.URLField(max_length=200) + name = models.CharField('Character name', max_length=200) + slug = models.SlugField(max_length=200, unique=True) + desc = models.TextField('Description', max_length=500, blank=True) + teams = models.ManyToManyField(Team, blank=True) + image = models.FilePathField( + 'Image file path', path="media/images", blank=True) + + def __str__(self): + return self.name - def __str__(self): - return self.name class Creator(models.Model): - cvid = models.CharField(max_length=15) - cvurl = models.URLField(max_length=200) - name = models.CharField('Creator name', max_length=200) - desc = models.TextField('Description', max_length=500, blank=True) - image = models.FilePathField('Image file path', path="media/images", blank=True) + cvid = models.CharField(max_length=15) + cvurl = models.URLField(max_length=200) + name = models.CharField('Creator name', max_length=200) + slug = models.SlugField(max_length=200, unique=True) + desc = models.TextField('Description', max_length=500, blank=True) + image = models.FilePathField( + 'Image file path', path="media/images", blank=True) + + def __str__(self): + return self.name - def __str__(self): - return self.name class Publisher(models.Model): - cvid = models.CharField(max_length=15) - cvurl = models.URLField(max_length=200) - name = models.CharField('Series name', max_length=200) - desc = models.TextField('Description', max_length=500, blank=True) - logo = models.FilePathField('Logo file path', path="media/images", blank=True) + cvid = models.CharField(max_length=15) + cvurl = models.URLField(max_length=200) + name = models.CharField('Series name', max_length=200) + slug = models.SlugField(max_length=200, unique=True) + desc = models.TextField('Description', max_length=500, blank=True) + logo = models.FilePathField( + 'Logo file path', path="media/images", blank=True) + + def __str__(self): + return self.name - def __str__(self): - return self.name class Series(models.Model): - cvid = models.CharField(max_length=15, blank=True) - cvurl = models.URLField(max_length=200, blank=True) - name = models.CharField('Series name', max_length=200) - publisher = models.ForeignKey(Publisher, on_delete=models.CASCADE, null=True, blank=True) - year = models.PositiveSmallIntegerField('year', choices=YEAR_CHOICES, default=datetime.datetime.now().year, blank=True) - desc = models.TextField('Description', max_length=500, blank=True) + cvid = models.CharField(max_length=15, blank=True) + cvurl = models.URLField(max_length=200, blank=True) + name = models.CharField('Series name', max_length=200) + slug = models.SlugField(max_length=200, unique=True) + publisher = models.ForeignKey( + Publisher, on_delete=models.CASCADE, null=True, blank=True) + year = models.PositiveSmallIntegerField( + 'year', choices=YEAR_CHOICES, default=datetime.datetime.now().year, blank=True) + desc = models.TextField('Description', max_length=500, blank=True) - def __str__(self): - return self.name + def __str__(self): + return self.name - def issue_numerical_order_set(self): - return self.issue_set.all().order_by('number') + def issue_numerical_order_set(self): + return self.issue_set.all().order_by('number') - def issue_count(self): - return self.issue_set.all().count() + def issue_count(self): + return self.issue_set.all().count() - def unread_issue_count(self): - return self.issue_set.exclude(status=2).count() + def unread_issue_count(self): + return self.issue_set.exclude(status=2).count() + + class Meta: + verbose_name_plural = "Series" - class Meta: - verbose_name_plural = "Series" class Issue(models.Model): - cvid = models.CharField('ComicVine ID', max_length=15, blank=True) - cvurl = models.URLField(max_length=200, blank=True) - series = models.ForeignKey(Series, on_delete=models.CASCADE, blank=True) - name = models.CharField('Issue name', max_length=200, blank=True) - number = models.PositiveSmallIntegerField('Issue number') - date = models.DateField('Cover date', blank=True) - desc = models.TextField('Description', max_length=500, blank=True) - arcs = models.ManyToManyField(Arc, blank=True) - characters = models.ManyToManyField(Character, blank=True) - teams = models.ManyToManyField(Team, blank=True) - file = models.FilePathField('File path', path="files/", recursive=True) - cover = models.FilePathField('Cover file path', path="media/images", blank=True) - status = models.PositiveSmallIntegerField('Status', choices=STATUS_CHOICES, default=0, blank=True) - leaf = models.PositiveSmallIntegerField(editable=False, default=1, blank=True) - page_count = models.PositiveSmallIntegerField(editable=False, default=1, blank=True) - - def __str__(self): - return self.series.name + ' #' + str(self.number) - - def get_absolute_url(self): - return reverse('author-detail', kwargs={'pk': self.pk}) - - def page_set(self): - comicfilehandler = ComicFileHandler() - comic = comicfilehandler.extract_comic(self.file, self.id) - - return comic + cvid = models.CharField('ComicVine ID', max_length=15, blank=True) + cvurl = models.URLField(max_length=200, blank=True) + series = models.ForeignKey(Series, on_delete=models.CASCADE, blank=True) + name = models.CharField('Issue name', max_length=200, blank=True) + slug = models.SlugField(max_length=200, unique=True) + number = models.CharField('Issue number', max_length=25) + date = models.DateField('Cover date', blank=True) + desc = models.TextField('Description', max_length=500, blank=True) + arcs = models.ManyToManyField(Arc, blank=True) + characters = models.ManyToManyField(Character, blank=True) + teams = models.ManyToManyField(Team, blank=True) + file = models.FilePathField('File path', path="files/", recursive=True) + cover = models.FilePathField( + 'Cover file path', path="media/images", blank=True) + status = models.PositiveSmallIntegerField( + 'Status', choices=STATUS_CHOICES, default=0, blank=True) + leaf = models.PositiveSmallIntegerField( + editable=False, default=1, blank=True) + page_count = models.PositiveSmallIntegerField( + editable=False, default=1, blank=True) + + def __str__(self): + return self.series.name + ' #' + str(self.number) + + def get_absolute_url(self): + return reverse('author-detail', kwargs={'pk': self.pk}) + + def page_set(self): + comicfilehandler = ComicFileHandler() + comic = comicfilehandler.extract_comic(self.file, self.id) + + return comic + class Roles(models.Model): - creator = models.ForeignKey(Creator, on_delete=models.CASCADE) - issue = models.ForeignKey(Issue, on_delete=models.CASCADE) - roles = MultiSelectField(choices=ROLE_CHOICES) + creator = models.ForeignKey(Creator, on_delete=models.CASCADE) + issue = models.ForeignKey(Issue, on_delete=models.CASCADE) + roles = MultiSelectField(choices=ROLE_CHOICES) - def __str__(self): - return self.issue.series.name + ' #' + str(self.issue.number) + ' - ' + self.creator.name + def __str__(self): + return self.issue.series.name + ' #' + str(self.issue.number) + ' - ' + self.creator.name - class Meta: - verbose_name_plural = "Roles" + class Meta: + verbose_name_plural = "Roles" diff --git a/comics/tasks.py b/comics/tasks.py index 7c55548..66e9c2a 100644 --- a/comics/tasks.py +++ b/comics/tasks.py @@ -8,6 +8,6 @@ def import_comic_files_task(): importer.import_comic_files() @task(name="reprocess_issue_task") -def reprocess_issue_task(issue_id): +def reprocess_issue_task(slug): comicimporter = ComicImporter() - comicimporter.reprocess_issue(issue_id) + comicimporter.reprocess_issue(slug) diff --git a/comics/templates/comics/index.html b/comics/templates/comics/index.html index 7781428..34cef47 100644 --- a/comics/templates/comics/index.html +++ b/comics/templates/comics/index.html @@ -17,7 +17,7 @@ {% for series in all_series %} {% if series.issue_set.all %}
{{ series.name }}
+{{ series.name }}
Released: {{ issue.date }}
{% endif %}{{ issue.page_count }} pages
- @@ -61,7 +61,7 @@