Adapt media to ISBN

This commit is contained in:
Alexandre Iooss 2019-08-11 09:22:22 +02:00
parent 79ad58997a
commit 2f872eccce
No known key found for this signature in database
GPG Key ID: 6C79278F3FCDCC02
20 changed files with 397 additions and 11 deletions

View File

@ -0,0 +1,5 @@
# -*- mode: python; coding: utf-8 -*-
# Copyright (C) 2017-2019 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later
default_app_config = 'logs.apps.LogsConfig'

View File

@ -1,3 +1,7 @@
# -*- mode: python; coding: utf-8 -*-
# Copyright (C) 2017-2019 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later
from django.apps import AppConfig from django.apps import AppConfig

View File

@ -0,0 +1,5 @@
# -*- mode: python; coding: utf-8 -*-
# Copyright (C) 2017-2019 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later
default_app_config = 'media.apps.MediaConfig'

View File

@ -17,18 +17,21 @@ class AuteurAdmin(VersionAdmin):
class MediaAdmin(VersionAdmin): class MediaAdmin(VersionAdmin):
list_display = ('titre', 'authors', 'cote') list_display = ('title', 'authors_list', 'side_title', 'isbn')
search_fields = ('titre', 'auteur__nom', 'cote') search_fields = ('title', 'authors__nom', 'side_title', 'subtitle', 'isbn')
autocomplete_fields = ('auteur',) autocomplete_fields = ('authors',)
date_hierarchy = 'publish_date'
def authors(self, obj): def authors_list(self, obj):
return ", ".join([a.nom for a in obj.auteur.all()]) return ", ".join([a.nom for a in obj.authors.all()])
authors_list.short_description = _('authors')
class EmpruntAdmin(VersionAdmin): class EmpruntAdmin(VersionAdmin):
list_display = ('media', 'user', 'date_emprunt', 'date_rendu', list_display = ('media', 'user', 'date_emprunt', 'date_rendu',
'permanencier_emprunt', 'permanencier_rendu_custom') 'permanencier_emprunt', 'permanencier_rendu_custom')
search_fields = ('media__titre', 'media__cote', 'user__username', search_fields = ('media__title', 'media__side_title', 'user__username',
'date_emprunt', 'date_rendu') 'date_emprunt', 'date_rendu')
date_hierarchy = 'date_emprunt' date_hierarchy = 'date_emprunt'
autocomplete_fields = ('media', 'user', 'permanencier_emprunt', autocomplete_fields = ('media', 'user', 'permanencier_emprunt',

View File

@ -1,3 +1,7 @@
# -*- mode: python; coding: utf-8 -*-
# Copyright (C) 2017-2019 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later
from django.apps import AppConfig from django.apps import AppConfig

55
media/fields.py Normal file
View File

@ -0,0 +1,55 @@
# -*- mode: python; coding: utf-8 -*-
# Copyright (C) 2017-2019 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later
"""
Based on https://github.com/secnot/django-isbn-field
"""
from django.core.validators import EMPTY_VALUES
from django.db.models import CharField
from django.utils.translation import gettext_lazy as _
from .validators import isbn_validator
class ISBNField(CharField):
description = _("ISBN-10 or ISBN-13")
def __init__(self, clean_isbn=True, *args, **kwargs):
self.clean_isbn = clean_isbn
kwargs['max_length'] = kwargs[
'max_length'] if 'max_length' in kwargs else 28
kwargs['verbose_name'] = kwargs[
'verbose_name'] if 'verbose_name' in kwargs else u'ISBN'
kwargs['validators'] = [isbn_validator]
super(ISBNField, self).__init__(*args, **kwargs)
def formfield(self, **kwargs):
defaults = {
'min_length': 10,
'validators': [isbn_validator],
}
defaults.update(kwargs)
return super(ISBNField, self).formfield(**defaults)
def deconstruct(self):
name, path, args, kwargs = super(ISBNField, self).deconstruct()
# Only include clean_isbn in kwarg if it's not the default value
if not self.clean_isbn:
kwargs['clean_isbn'] = self.clean_isbn
return name, path, args, kwargs
def pre_save(self, model_instance, add):
"""
Remove dashes, spaces, and convert isbn to uppercase before saving
when clean_isbn is enabled
"""
value = getattr(model_instance, self.attname)
if self.clean_isbn and value not in EMPTY_VALUES:
cleaned_isbn = value.replace(' ', '').replace('-', '').upper()
setattr(model_instance, self.attname, cleaned_isbn)
return super(ISBNField, self).pre_save(model_instance, add)
def __unicode__(self):
return self.value

View File

@ -0,0 +1,18 @@
# Generated by Django 2.2.4 on 2019-08-11 07:01
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('media', '0009_auto_20190802_1455'),
]
operations = [
migrations.RenameField(
model_name='media',
old_name='titre',
new_name='title',
),
]

View File

@ -0,0 +1,23 @@
# Generated by Django 2.2.4 on 2019-08-11 07:03
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('media', '0010_auto_20190811_0901'),
]
operations = [
migrations.AddField(
model_name='media',
name='subtitle',
field=models.CharField(blank=True, max_length=255, null=True, verbose_name='subtitle'),
),
migrations.AlterField(
model_name='media',
name='title',
field=models.CharField(max_length=255, verbose_name='title'),
),
]

View File

@ -0,0 +1,18 @@
# Generated by Django 2.2.4 on 2019-08-11 07:05
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('media', '0011_auto_20190811_0903'),
]
operations = [
migrations.AddField(
model_name='media',
name='external_url',
field=models.URLField(blank=True, null=True, verbose_name='external URL'),
),
]

View File

@ -0,0 +1,18 @@
# Generated by Django 2.2.4 on 2019-08-11 07:07
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('media', '0012_media_external_url'),
]
operations = [
migrations.RenameField(
model_name='media',
old_name='auteur',
new_name='authors',
),
]

View File

@ -0,0 +1,23 @@
# Generated by Django 2.2.4 on 2019-08-11 07:08
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('media', '0013_auto_20190811_0907'),
]
operations = [
migrations.AddField(
model_name='media',
name='number_of_pages',
field=models.PositiveIntegerField(blank=True, null=True, verbose_name='number of pages'),
),
migrations.AlterField(
model_name='media',
name='authors',
field=models.ManyToManyField(to='media.Auteur', verbose_name='authors'),
),
]

View File

@ -0,0 +1,18 @@
# Generated by Django 2.2.4 on 2019-08-11 07:09
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('media', '0014_auto_20190811_0908'),
]
operations = [
migrations.AddField(
model_name='media',
name='publish_date',
field=models.DateField(blank=True, null=True, verbose_name='publish date'),
),
]

View File

@ -0,0 +1,20 @@
# Generated by Django 2.2.4 on 2019-08-11 07:17
from django.db import migrations
import media.fields
import media.validators
class Migration(migrations.Migration):
dependencies = [
('media', '0015_media_publish_date'),
]
operations = [
migrations.AddField(
model_name='media',
name='isbn',
field=media.fields.ISBNField(blank=True, max_length=28, null=True, validators=[media.validators.isbn_validator], verbose_name='ISBN'),
),
]

View File

@ -0,0 +1,20 @@
# Generated by Django 2.2.4 on 2019-08-11 07:18
from django.db import migrations
import media.fields
import media.validators
class Migration(migrations.Migration):
dependencies = [
('media', '0016_media_isbn'),
]
operations = [
migrations.AlterField(
model_name='media',
name='isbn',
field=media.fields.ISBNField(blank=True, help_text='You may be able to scan it from a bar code.', max_length=28, null=True, validators=[media.validators.isbn_validator], verbose_name='ISBN'),
),
]

View File

@ -0,0 +1,18 @@
# Generated by Django 2.2.4 on 2019-08-11 07:18
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('media', '0017_auto_20190811_0918'),
]
operations = [
migrations.RenameField(
model_name='media',
old_name='cote',
new_name='side_title',
),
]

View File

@ -0,0 +1,18 @@
# Generated by Django 2.2.4 on 2019-08-11 07:19
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('media', '0018_auto_20190811_0918'),
]
operations = [
migrations.AlterField(
model_name='media',
name='side_title',
field=models.CharField(max_length=255, verbose_name='side title'),
),
]

View File

@ -6,6 +6,8 @@ from django.core.validators import MinValueValidator
from django.db import models from django.db import models
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from .fields import ISBNField
class Auteur(models.Model): class Auteur(models.Model):
nom = models.CharField(max_length=255, unique=True) nom = models.CharField(max_length=255, unique=True)
@ -19,12 +21,48 @@ class Auteur(models.Model):
class Media(models.Model): class Media(models.Model):
titre = models.CharField(max_length=255) isbn = ISBNField(
cote = models.CharField(max_length=31) _('ISBN'),
auteur = models.ManyToManyField('Auteur') help_text=_('You may be able to scan it from a bar code.'),
blank=True,
null=True,
)
title = models.CharField(
verbose_name=_('title'),
max_length=255,
)
subtitle = models.CharField(
verbose_name=_('subtitle'),
max_length=255,
blank=True,
null=True,
)
external_url = models.URLField(
verbose_name=_('external URL'),
blank=True,
null=True,
)
side_title = models.CharField(
verbose_name=_('side title'),
max_length=255,
)
authors = models.ManyToManyField(
'Auteur',
verbose_name=_('authors'),
)
number_of_pages = models.PositiveIntegerField(
verbose_name=_('number of pages'),
blank=True,
null=True,
)
publish_date = models.DateField(
verbose_name=_('publish date'),
blank=True,
null=True,
)
def __str__(self): def __str__(self):
return str(self.titre) + ' - ' + str(self.auteur.all().first()) return str(self.title) + ' - ' + str(self.authors.all().first())
class Meta: class Meta:
verbose_name = _("medium") verbose_name = _("medium")

View File

@ -0,0 +1,46 @@
// curl 'https://openlibrary.org/api/books?bibkeys=ISBN:0201558025&format=json&jscmd=data'
a = {
"ISBN:0201558025": {
"publishers": [{"name": "Addison-Wesley"}],
"pagination": "xiii, 657 p. :",
"identifiers": {
"lccn": ["93040325"],
"openlibrary": ["OL1429049M"],
"isbn_10": ["0201558025"],
"wikidata": ["Q15303722"],
"goodreads": ["112243"],
"librarything": ["45844"]
},
//"subtitle": "a foundation for computer science",
//"title": "Concrete mathematics",
//"url": "https://openlibrary.org/books/OL1429049M/Concrete_mathematics",
"classifications": {"dewey_decimal_class": ["510"], "lc_classifications": ["QA39.2 .G733 1994"]},
"notes": "Includes bibliographical references (p. 604-631) and index.",
"number_of_pages": 657,
"cover": {
"small": "https://covers.openlibrary.org/b/id/135182-S.jpg",
"large": "https://covers.openlibrary.org/b/id/135182-L.jpg",
"medium": "https://covers.openlibrary.org/b/id/135182-M.jpg"
},
"subjects": [{
"url": "https://openlibrary.org/subjects/computer_science",
"name": "Computer science"
}, {"url": "https://openlibrary.org/subjects/mathematics", "name": "Mathematics"}],
"publish_date": "1994",
"key": "/books/OL1429049M",
"authors": [{
"url": "https://openlibrary.org/authors/OL720958A/Ronald_L._Graham",
"name": "Ronald L. Graham"
}, {
"url": "https://openlibrary.org/authors/OL229501A/Donald_Knuth",
"name": "Donald Knuth"
}, {"url": "https://openlibrary.org/authors/OL2669938A/Oren_Patashnik", "name": "Oren Patashnik"}],
"by_statement": "Ronald L. Graham, Donald E. Knuth, Oren Patashnik.",
"publish_places": [{"name": "Reading, Mass"}],
"ebooks": [{
"formats": {},
"preview_url": "https://archive.org/details/concretemathemat00grah_444",
"availability": "restricted"
}]
}
}

31
media/validators.py Normal file
View File

@ -0,0 +1,31 @@
# -*- mode: python; coding: utf-8 -*-
# Copyright (C) 2017-2019 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later
"""
Based on https://github.com/secnot/django-isbn-field
"""
from django.core.exceptions import ValidationError
from django.utils.translation import ugettext_lazy as _
from six import string_types
from stdnum import isbn
def isbn_validator(raw_isbn):
"""Check string is a valid ISBN number"""
isbn_to_check = raw_isbn.replace('-', '').replace(' ', '')
if not isinstance(isbn_to_check, string_types):
raise ValidationError(_(u'Invalid ISBN: Not a string'))
if len(isbn_to_check) != 10 and len(isbn_to_check) != 13:
raise ValidationError(_(u'Invalid ISBN: Wrong length'))
if not isbn.is_valid(isbn_to_check):
raise ValidationError(_(u'Invalid ISBN: Failed checksum'))
if isbn_to_check != isbn_to_check.upper():
raise ValidationError(_(u'Invalid ISBN: Only upper case allowed'))
return True

View File

@ -4,4 +4,5 @@ Pillow==5.4.1
pytz==2019.1 pytz==2019.1
six==1.12.0 six==1.12.0
sqlparse==0.2.4 sqlparse==0.2.4
django-reversion==3.0.3 django-reversion==3.0.3
python-stdnum==1.10