mirror of
https://gitlab.crans.org/mediatek/med.git
synced 2024-11-26 21:27:11 +00:00
Adapt media to ISBN
This commit is contained in:
parent
79ad58997a
commit
2f872eccce
@ -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'
|
@ -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
|
||||||
|
|
||||||
|
|
||||||
|
@ -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'
|
@ -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',
|
||||||
|
@ -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
55
media/fields.py
Normal 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
|
18
media/migrations/0010_auto_20190811_0901.py
Normal file
18
media/migrations/0010_auto_20190811_0901.py
Normal 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',
|
||||||
|
),
|
||||||
|
]
|
23
media/migrations/0011_auto_20190811_0903.py
Normal file
23
media/migrations/0011_auto_20190811_0903.py
Normal 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'),
|
||||||
|
),
|
||||||
|
]
|
18
media/migrations/0012_media_external_url.py
Normal file
18
media/migrations/0012_media_external_url.py
Normal 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'),
|
||||||
|
),
|
||||||
|
]
|
18
media/migrations/0013_auto_20190811_0907.py
Normal file
18
media/migrations/0013_auto_20190811_0907.py
Normal 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',
|
||||||
|
),
|
||||||
|
]
|
23
media/migrations/0014_auto_20190811_0908.py
Normal file
23
media/migrations/0014_auto_20190811_0908.py
Normal 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'),
|
||||||
|
),
|
||||||
|
]
|
18
media/migrations/0015_media_publish_date.py
Normal file
18
media/migrations/0015_media_publish_date.py
Normal 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'),
|
||||||
|
),
|
||||||
|
]
|
20
media/migrations/0016_media_isbn.py
Normal file
20
media/migrations/0016_media_isbn.py
Normal 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'),
|
||||||
|
),
|
||||||
|
]
|
20
media/migrations/0017_auto_20190811_0918.py
Normal file
20
media/migrations/0017_auto_20190811_0918.py
Normal 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'),
|
||||||
|
),
|
||||||
|
]
|
18
media/migrations/0018_auto_20190811_0918.py
Normal file
18
media/migrations/0018_auto_20190811_0918.py
Normal 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',
|
||||||
|
),
|
||||||
|
]
|
18
media/migrations/0019_auto_20190811_0919.py
Normal file
18
media/migrations/0019_auto_20190811_0919.py
Normal 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'),
|
||||||
|
),
|
||||||
|
]
|
@ -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")
|
||||||
|
46
media/static/media/isbn_fetcher.js
Normal file
46
media/static/media/isbn_fetcher.js
Normal 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
31
media/validators.py
Normal 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
|
@ -5,3 +5,4 @@ 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
|
Loading…
Reference in New Issue
Block a user