fmartingr
/
shelfzilla
Archived
1
0
Fork 0

- Model manga.Volume: + name, number/pages/retail_price not mandatory

- Added custom admin app
- Added custom admin action to easily bulk modify volume series
- Volume name for unique volumes or special ones that have no number
This commit is contained in:
Felipe Martin 2014-04-25 12:47:26 +02:00
parent 34c0a73cdb
commit 718ece075e
11 changed files with 307 additions and 9 deletions

View File

View File

@ -0,0 +1 @@
# Django

View File

@ -0,0 +1,10 @@
from django.conf.urls import patterns, url
from .views import VolumeChangeSeriesView
urlpatterns = patterns(
'',
url(r'^manga/volume/change_series/$',
VolumeChangeSeriesView.as_view(),
name="_admin.manga.volume.change_series"),
)

View File

@ -0,0 +1,44 @@
from django.shortcuts import render_to_response
from django.template import RequestContext
from django.utils.translation import ugettext as _
from django.contrib import messages
from django.http import HttpResponseForbidden, HttpResponseRedirect
from shelfzilla.views import View
from shelfzilla.apps.manga.models import Volume, Series
class VolumeChangeSeriesView(View):
template = '_admin/volumes/change_series.html'
def dispatch(self, request, *args, **kwargs):
if not request.user.is_staff:
return HttpResponseForbidden()
else:
return super(VolumeChangeSeriesView, self).dispatch(
request, *args, **kwargs)
def get(self, request):
volumes = Volume.objects.filter(
pk__in=request.GET['volumes'].split(','))
data = {
'volumes': volumes,
'series': Series.objects.all,
}
ctx = RequestContext(request, data)
return render_to_response(self.template, context_instance=ctx)
def post(self, request):
volumes = Volume.objects.filter(
pk__in=request.GET['volumes'].split(','))
series = Series.objects.get(pk=int(request.POST['series_pk']))
for vol in volumes:
vol.series = series
vol.save()
messages.success(request, _('Volume series changed'))
return HttpResponseRedirect('/admin/manga/volume/')

View File

@ -1,5 +1,8 @@
from django.contrib import admin
from django.utils.translation import ugettext_lazy as _
from django.core.urlresolvers import reverse
from django.contrib.contenttypes.models import ContentType
from django.http import HttpResponseRedirect
import reversion
from .models import Publisher, Series, Volume, Person
@ -22,19 +25,29 @@ class SeriesAdmin(reversion.VersionAdmin):
search_filters = ('hidden', )
def volumes_count(self, obj):
return obj.volumes.distinct('number').count()
return obj.volumes.count()
volumes_count.short_description = _('Volumes')
admin.site.register(Series, SeriesAdmin)
class VolumeAdmin(reversion.VersionAdmin):
# list_display_links = ('number', )
list_display = ('series', 'number', 'release_date',)
list_display = ('series', 'publisher', 'number', 'name', 'release_date',)
search_fields = ('number', 'series__name', )
# list_filter = ('series', )
list_filter = ('series', 'for_review', )
# list_editable = ('series', )
actions = ['change_series']
def change_series(self, request, queryset):
selected = request.POST.getlist(admin.ACTION_CHECKBOX_NAME)
return HttpResponseRedirect(
"{}{}".format(
reverse('_admin.manga.volume.change_series'),
"?volumes={}".format(",".join(selected))
)
)
change_series.short_description = _('Change volume series')
admin.site.register(Volume, VolumeAdmin)

View File

@ -0,0 +1,175 @@
# -*- coding: utf-8 -*-
from south.utils import datetime_utils as datetime
from south.db import db
from south.v2 import SchemaMigration
from django.db import models
class Migration(SchemaMigration):
def forwards(self, orm):
# Adding field 'Volume.name'
db.add_column(u'manga_volume', 'name',
self.gf('django.db.models.fields.CharField')(max_length=64, null=True, blank=True),
keep_default=False)
# Changing field 'Volume.number'
db.alter_column(u'manga_volume', 'number', self.gf('django.db.models.fields.IntegerField')(null=True))
def backwards(self, orm):
# Deleting field 'Volume.name'
db.delete_column(u'manga_volume', 'name')
# Changing field 'Volume.number'
db.alter_column(u'manga_volume', 'number', self.gf('django.db.models.fields.IntegerField')(default=0))
models = {
u'auth.group': {
'Meta': {'object_name': 'Group'},
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
},
u'auth.permission': {
'Meta': {'ordering': "(u'content_type__app_label', u'content_type__model', u'codename')", 'unique_together': "((u'content_type', u'codename'),)", 'object_name': 'Permission'},
'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['contenttypes.ContentType']"}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
},
u'auth.user': {
'Meta': {'object_name': 'User'},
'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
'groups': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "u'user_set'", 'blank': 'True', 'to': u"orm['auth.Group']"}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "u'user_set'", 'blank': 'True', 'to': u"orm['auth.Permission']"}),
'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '75'})
},
u'contenttypes.contenttype': {
'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
},
'filer.file': {
'Meta': {'object_name': 'File'},
'_file_size': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}),
'description': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
'file': ('django.db.models.fields.files.FileField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
'folder': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'all_files'", 'null': 'True', 'to': "orm['filer.Folder']"}),
'has_all_mandatory_data': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'is_public': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'modified_at': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '255', 'blank': 'True'}),
'original_filename': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
'owner': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'owned_files'", 'null': 'True', 'to': u"orm['auth.User']"}),
'polymorphic_ctype': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'polymorphic_filer.file_set'", 'null': 'True', 'to': u"orm['contenttypes.ContentType']"}),
'sha1': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '40', 'blank': 'True'}),
'uploaded_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'})
},
'filer.folder': {
'Meta': {'ordering': "('name',)", 'unique_together': "(('parent', 'name'),)", 'object_name': 'Folder'},
'created_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'level': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}),
'lft': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}),
'modified_at': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'owner': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'filer_owned_folders'", 'null': 'True', 'to': u"orm['auth.User']"}),
'parent': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'children'", 'null': 'True', 'to': "orm['filer.Folder']"}),
'rght': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}),
'tree_id': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}),
'uploaded_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'})
},
'filer.image': {
'Meta': {'object_name': 'Image', '_ormbases': ['filer.File']},
'_height': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}),
'_width': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}),
'author': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
'date_taken': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
'default_alt_text': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
'default_caption': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
u'file_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['filer.File']", 'unique': 'True', 'primary_key': 'True'}),
'must_always_publish_author_credit': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'must_always_publish_copyright': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'subject_location': ('django.db.models.fields.CharField', [], {'default': 'None', 'max_length': '64', 'null': 'True', 'blank': 'True'})
},
u'manga.person': {
'Meta': {'ordering': "['name']", 'object_name': 'Person'},
'for_review': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'for_review_comment': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
'hidden': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '256'}),
'slug': ('django.db.models.fields.SlugField', [], {'max_length': '50', 'null': 'True', 'blank': 'True'})
},
u'manga.publisher': {
'Meta': {'ordering': "['name']", 'object_name': 'Publisher'},
'for_review': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'for_review_comment': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
'hidden': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
'slug': ('django.db.models.fields.SlugField', [], {'max_length': '50', 'null': 'True', 'blank': 'True'}),
'url': ('django.db.models.fields.URLField', [], {'max_length': '200', 'null': 'True', 'blank': 'True'})
},
u'manga.series': {
'Meta': {'ordering': "['name']", 'object_name': 'Series'},
'art': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'artist_of'", 'null': 'True', 'to': u"orm['manga.Person']"}),
'cover': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['filer.Image']", 'null': 'True', 'blank': 'True'}),
'finished': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'folder': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['filer.Folder']", 'null': 'True', 'blank': 'True'}),
'for_review': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'for_review_comment': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
'hidden': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '256'}),
'original_publisher': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'original_series'", 'null': 'True', 'to': u"orm['manga.Publisher']"}),
'slug': ('django.db.models.fields.SlugField', [], {'max_length': '256', 'null': 'True', 'blank': 'True'}),
'story': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'scriptwriter_of'", 'null': 'True', 'to': u"orm['manga.Person']"}),
'summary': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'})
},
u'manga.userhavevolume': {
'Meta': {'object_name': 'UserHaveVolume'},
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'have_volumes'", 'to': u"orm['auth.User']"}),
'volume': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'owned_by'", 'to': u"orm['manga.Volume']"})
},
u'manga.userwishlistvolume': {
'Meta': {'object_name': 'UserWishlistVolume'},
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'wishlisted_volumes'", 'to': u"orm['auth.User']"}),
'volume': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'wishlisted_by'", 'to': u"orm['manga.Volume']"})
},
u'manga.volume': {
'Meta': {'ordering': "['series__name', 'number']", 'object_name': 'Volume'},
'cover': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['filer.Image']", 'null': 'True', 'blank': 'True'}),
'for_review': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'for_review_comment': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
'hidden': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'isbn_10': ('django.db.models.fields.CharField', [], {'max_length': '10', 'null': 'True', 'blank': 'True'}),
'isbn_13': ('django.db.models.fields.CharField', [], {'max_length': '13', 'null': 'True', 'blank': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '64', 'null': 'True', 'blank': 'True'}),
'number': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}),
'pages': ('django.db.models.fields.IntegerField', [], {'null': 'True'}),
'publisher': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'volumes'", 'to': u"orm['manga.Publisher']"}),
'release_date': ('django.db.models.fields.DateField', [], {'null': 'True'}),
'retail_price': ('django.db.models.fields.DecimalField', [], {'null': 'True', 'max_digits': '5', 'decimal_places': '2'}),
'series': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'volumes'", 'to': u"orm['manga.Series']"})
}
}
complete_apps = ['manga']

View File

@ -1,7 +1,8 @@
from django.db import models
from django.utils.translation import ugettext_lazy as _
from django.conf import settings
from django.db.models.signals import post_save
from django.db.models.signals import post_save, post_delete
from django.utils.text import slugify
from filer.fields.image import FilerImageField
from filer.models.foldermodels import Folder
@ -95,7 +96,8 @@ class Series(Model):
class Volume(Model):
number = models.IntegerField(_('Number'))
number = models.IntegerField(_('Number'), null=True, blank=True)
name = models.CharField(_('Name'), max_length=64, null=True, blank=True)
series = models.ForeignKey(Series, related_name="volumes")
publisher = models.ForeignKey(Publisher, related_name="volumes")
cover = FilerImageField(null=True, blank=True)
@ -105,8 +107,9 @@ class Volume(Model):
_('ISBN-13'), max_length=13, blank=True, null=True)
retail_price = models.DecimalField(
_('Retail price'), max_digits=5, decimal_places=2, null=True)
pages = models.IntegerField(_('Pages'), null=True)
_('Retail price'), max_digits=5, decimal_places=2,
null=True, blank=True)
pages = models.IntegerField(_('Pages'), null=True, blank=True)
release_date = models.DateField(_('Release date'), null=True)
def __unicode__(self):
@ -199,11 +202,25 @@ def volume_check_filer(sender, instance, created, **kwargs):
instance.cover.save()
# Check filename
cover_name = '{:03}'.format(instance.number)
if instance.name:
cover_name = slugify(instance.name)
elif instance.number:
cover_name = '{:03}'.format(instance.number)
if instance.cover.name != cover_name:
instance.cover.name = cover_name
instance.cover.save()
def series_delete_folder(sender, instance, using, **kwargs):
if instance.folder:
instance.folder.delete()
def volume_delete_cover(sender, instance, **kwargs):
if instance.cover:
instance.cover.delete()
post_save.connect(series_check_filer, sender=Series)
post_save.connect(volume_check_filer, sender=Volume)
post_delete.connect(series_delete_folder, sender=Series)
post_delete.connect(volume_delete_cover, sender=Volume)

View File

@ -56,6 +56,7 @@ INSTALLED_APPS = (
'south',
# Apps
'shelfzilla.apps._admin',
'shelfzilla.apps.config',
'shelfzilla.apps.users',
'shelfzilla.apps.homepage',

View File

@ -0,0 +1,31 @@
{% extends "admin/base_site.html" %}
{% load i18n %}
{% block title %}{% trans "Change volume series" %} | {{ block.super }}{% endblock %}
{% block content %}
<div class="alert alert-block alert-info">
<h4>{% trans "Change volume series" %}</h4>
<p>{% trans "You are about to change the series of this volumes:" %}</p>
<ul>
{% for volume in volumes %}
<li><a href="/admin/manga/volume/{{ volume.pk }}/">{{ volume.series }} {% if volume.number %}#{{ volume.number }}{% endif %}
{% if volume.name %}- {{ volume.name }}{% endif %}</a></li>
{% endfor %}
</ul>
<br />
<form method="post" class="form">
{% csrf_token %}
<div class="form-control">
<label>{% trans "Change to: " %}</label>
<select name="series_pk">
{% for s in series %}
<option value="{{ s.pk }}">{{ s.name }}</option>
{% endfor %}
</select>
</div>
<br />
<button class="btn btn-info" type="submit">{% trans "Change" %}</button>
</form>
</div>
{% endblock %}

View File

@ -21,7 +21,12 @@
{% endif %}
</div>
<div class="text-center">
{% if volume.number %}
<div><strong>#{{ volume.number }}</strong></div>
{% endif %}
{% if volume.name %}
<div><strong>{{ volume.name }}</strong></div>
{% endif %}
<img src="{% thumbnail volume.cover 150x150 %}" class="max-width-80" />
</div>
</div>

View File

@ -16,6 +16,7 @@ urlpatterns = patterns(
url(r'^volumes/', include('shelfzilla.apps.manga.urls.volumes')),
url(r'^publishers/', include('shelfzilla.apps.manga.urls.publishers')),
url(r'^$', include('shelfzilla.apps.homepage.urls')),
url(r'^_admin/', include('shelfzilla.apps._admin.urls')),
url(r'^admin/', include(admin.site.urls)),
)