Allow fetching data from Decitre given ISBN. #28

This commit is contained in:
Gabriel Augendre 2018-06-15 23:23:59 +02:00
parent f98bc4f14d
commit dc2ae9ea81
7 changed files with 141 additions and 21 deletions

View file

@ -14,6 +14,7 @@ uuid = "*"
django-anymail = {extras = ["mailgun"]}
whitenoise = "*"
django-import-export = "*"
"beautifulsoup4" = "*"
[dev-packages]
selenium = "*"

29
Pipfile.lock generated
View file

@ -1,7 +1,7 @@
{
"_meta": {
"hash": {
"sha256": "84c82ea544617a654420f88f0121bd55a61f6aa48bf9e0da6e74670dad53743c"
"sha256": "6a69113216b0e4cdef5dca808dac46d78b2c79edf087766d6608e8abcc721a9e"
},
"pipfile-spec": 6,
"requires": {
@ -16,6 +16,15 @@
]
},
"default": {
"beautifulsoup4": {
"hashes": [
"sha256:11a9a27b7d3bddc6d86f59fb76afb70e921a25ac2d6cc55b40d072bd68435a76",
"sha256:7015e76bf32f1f574636c4288399a6de66ce08fb7b2457f628a8d70c0fbabb11",
"sha256:808b6ac932dccb0a4126558f7dfdcf41710dd44a4ef497a0bb59a77f9f078e89"
],
"index": "pypi",
"version": "==4.6.0"
},
"certifi": {
"hashes": [
"sha256:13e698f54293db9f89122b0581843a782ad0934a4fe0172d2a980ba77fc61bb7",
@ -99,10 +108,10 @@
},
"idna": {
"hashes": [
"sha256:2c6a5de3089009e3da7c5dde64a141dbc8551d5b7f6cf4ed7c2568d0cc520a8f",
"sha256:8c7309c718f94b3a625cb648ace320157ad16ff131ae0af362c9f21b80ef6ec4"
"sha256:156a6814fb5ac1fc6850fb002e0852d56c0c8d2531923a51032d1b70760e186e",
"sha256:684a38a6f903c1d71d6d5fac066b58d7768af4de2b832e426ec79c30daa94a16"
],
"version": "==2.6"
"version": "==2.7"
},
"jdcal": {
"hashes": [
@ -185,10 +194,10 @@
},
"requests": {
"hashes": [
"sha256:6a1b267aa90cac58ac3a765d067950e7dbbf75b1da07e895d1f594193a40a38b",
"sha256:9c443e7324ba5b85070c4a818ade28bfabedf16ea10206da1132edaa6dda237e"
"sha256:63b52e3c866428a224f97cab011de738c36aec0185aa91cfacd418b5d58911d1",
"sha256:ec22d826a36ed72a7358ff3fe56cbd4ba69dd7a6718ffd450ff0e9df7a47ce6a"
],
"version": "==2.18.4"
"version": "==2.19.1"
},
"six": {
"hashes": [
@ -211,10 +220,10 @@
},
"urllib3": {
"hashes": [
"sha256:06330f386d6e4b195fbfc736b297f58c5a892e4440e54d294d7004e3a9bbea1b",
"sha256:cc44da8e1145637334317feebd728bd869a35285b93cbb4cca2577da7e62db4f"
"sha256:a68ac5e15e76e7e5dd2b8f94007233e01effe3e50e8daddf69acfd81cb686baf",
"sha256:b5725a0bd4ba422ab0e66e89e030c806576753ea3ee08554382c14e685d117b5"
],
"version": "==1.22"
"version": "==1.23"
},
"uuid": {
"hashes": [

View file

@ -1,7 +1,46 @@
document.addEventListener("DOMContentLoaded", function (event) {
console.log('document loaded');
var isbnButton = document.querySelector('#id_isbn_button');
var isbn = document.querySelector('#id_isbn');
isbn.addEventListener('change', function (event) {
isbnButton.addEventListener('click', function (event) {
$.get("/isbn_api/" + isbn.value, {}, function (data, status, xhr) {
if (data.error) {
isbn.classList.add('is-invalid');
isbn.classList.remove('is-valid');
document.querySelector('#id_isbn_invalid_feedback').style.display = 'block';
document.querySelector('#id_isbn_error_text').textContent = data.error;
return;
}
isbn.classList.remove('is-invalid');
isbn.classList.add('is-valid');
document.querySelector('#id_isbn_invalid_feedback').style.display = 'none';
document.querySelector('#id_title').value = data.title;
document.querySelector('#id_authors').value = data.authors;
document.querySelector('#id_publication_year').value = data.year;
document.querySelector('#id_price').value = data.price;
var editorValue = "";
var editorIsOther = false;
for (var option of document.querySelector('#id_editor').children) {
if (editorValue === "" && option.firstChild.data.toLowerCase().indexOf('autre') !== -1) {
editorValue = option.value;
editorIsOther = true;
}
if (option.firstChild.data.toLowerCase() === data.editor.toLowerCase()) {
editorValue = option.value;
editorIsOther = false;
}
}
document.querySelector('#id_editor').value = editorValue;
event = document.createEvent("HTMLEvents");
event.initEvent("change", true, true);
event.eventName = "change";
document.querySelector('#id_editor').dispatchEvent(event);
if (editorIsOther) {
document.querySelector('#id_other_editor').value = data.editor
}
});
});
});

View file

@ -27,7 +27,27 @@
</div>
<div class="form-row">
<div class="col-12">
{% bootstrap_field form.isbn %}
<div class="form-group">
{% bootstrap_label content=form.isbn.label label_for=id_isbn %}
<div class="input-group">
<input name="isbn" maxlength="20"
class="form-control"
placeholder="{{ form.isbn.label }}"
required="" id="id_isbn" type="text">
<div class="input-group-append">
<button class="btn btn-outline-secondary" type="button" id="id_isbn_button">
Chercher sur Decitre
</button>
</div>
</div>
<div class="invalid-feedback" id="id_isbn_invalid_feedback">
Erreur lors de la recherche. Veuillez saisir les informations du livre à la main.<br>
Données techniques : <span id="id_isbn_error_text"></span>
</div>
<small class="form-text text-muted">
{{ form.isbn.help_text|safe }}
</small>
</div>
</div>
</div>
<div class="form-row">

View file

@ -43,8 +43,7 @@
{% block end_js %}
{% endblock %}
<script src="https://code.jquery.com/jquery-3.3.1.slim.min.js"
integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo"
<script src="https://code.jquery.com/jquery-3.3.1.min.js"
crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.0/umd/popper.min.js"
integrity="sha384-cs/chFZiN24E4KMATLdqdvsezGxaGsi4hLGOzlXwp5UZB1LY//20VyM2taTB4QvJ"

View file

@ -1,7 +1,7 @@
from django.urls import path
from manuels.views import AddBookView, ListBooksView, clear_teacher_view, AddSuppliesView, EditBookView, \
EditSuppliesView, DeleteBookView, DeleteSuppliesView, ConfirmTeacherView
EditSuppliesView, DeleteBookView, DeleteSuppliesView, ConfirmTeacherView, isbn_api
urlpatterns = [
path('teacher/<uuid:pk>/add_book', AddBookView.as_view(), name='add_book'),
@ -13,4 +13,5 @@ urlpatterns = [
path('teacher/<uuid:teacher_pk>/supplies/<int:pk>/delete', DeleteSuppliesView.as_view(), name='delete_supplies'),
path('teacher/<uuid:pk>/confirm', ConfirmTeacherView.as_view(), name='confirm_teacher'),
path('clear', clear_teacher_view, name='clear_teacher'),
path('isbn_api/<str:isbn>', isbn_api, name='isbn_api'),
]

View file

@ -1,6 +1,10 @@
import re
import bs4
import requests
from django.contrib import messages
from django.contrib.auth.mixins import PermissionRequiredMixin
from django.http import HttpResponseRedirect
from django.http import HttpResponseRedirect, JsonResponse
from django.shortcuts import get_object_or_404, redirect
from django.urls import reverse
from django.views.generic import CreateView, ListView, UpdateView, DeleteView, FormView, DetailView, TemplateView
@ -220,3 +224,50 @@ class ConfirmTeacherView(BaseTeacherView, UpdateView):
self.object.send_confirmation(request=self.request)
messages.success(self.request, "Vos listes ont été validées. Votre documentaliste a été notifiée par email.")
return response
def isbn_api(request, isbn):
res = requests.get(f'https://www.decitre.fr/livres/{isbn}.html')
try:
res.raise_for_status()
except Exception as exc:
return JsonResponse({
'error': str(exc)
})
decitre_soup = bs4.BeautifulSoup(res.text, "html.parser")
title = decitre_soup.select('h1.product-title')
if title:
title = title[0]
title.span.extract()
title = title.getText().strip()
authors = decitre_soup.select('h2.authors')
if authors:
authors = authors[0]
authors = authors.getText().strip()
price = decitre_soup.select('.product-add-to-cart-wrapper div.price span.final-price')
if price:
price = price[0]
price = price.getText().replace('', '').replace(',', '.').strip()
year = None
editor = None
extra_info = decitre_soup.select('ul.extra-infos.hide-on-responsive')
if extra_info:
extra_info = extra_info[0].getText().strip()
matches = re.match('^(?P<editor>.+)\nParu le : \d{2}/\d{2}/(?P<year>\d{4})$', extra_info)
groups = matches.groupdict()
year = groups.get('year')
editor = groups.get('editor')
return JsonResponse({
'title': title,
'authors': authors,
'isbn': isbn,
'price': float(price),
'year': year,
'editor': editor,
})