Allow fetching data from Decitre given ISBN. #28
This commit is contained in:
parent
f98bc4f14d
commit
dc2ae9ea81
7 changed files with 141 additions and 21 deletions
1
Pipfile
1
Pipfile
|
@ -14,6 +14,7 @@ uuid = "*"
|
||||||
django-anymail = {extras = ["mailgun"]}
|
django-anymail = {extras = ["mailgun"]}
|
||||||
whitenoise = "*"
|
whitenoise = "*"
|
||||||
django-import-export = "*"
|
django-import-export = "*"
|
||||||
|
"beautifulsoup4" = "*"
|
||||||
|
|
||||||
[dev-packages]
|
[dev-packages]
|
||||||
selenium = "*"
|
selenium = "*"
|
||||||
|
|
29
Pipfile.lock
generated
29
Pipfile.lock
generated
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"_meta": {
|
"_meta": {
|
||||||
"hash": {
|
"hash": {
|
||||||
"sha256": "84c82ea544617a654420f88f0121bd55a61f6aa48bf9e0da6e74670dad53743c"
|
"sha256": "6a69113216b0e4cdef5dca808dac46d78b2c79edf087766d6608e8abcc721a9e"
|
||||||
},
|
},
|
||||||
"pipfile-spec": 6,
|
"pipfile-spec": 6,
|
||||||
"requires": {
|
"requires": {
|
||||||
|
@ -16,6 +16,15 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"default": {
|
"default": {
|
||||||
|
"beautifulsoup4": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:11a9a27b7d3bddc6d86f59fb76afb70e921a25ac2d6cc55b40d072bd68435a76",
|
||||||
|
"sha256:7015e76bf32f1f574636c4288399a6de66ce08fb7b2457f628a8d70c0fbabb11",
|
||||||
|
"sha256:808b6ac932dccb0a4126558f7dfdcf41710dd44a4ef497a0bb59a77f9f078e89"
|
||||||
|
],
|
||||||
|
"index": "pypi",
|
||||||
|
"version": "==4.6.0"
|
||||||
|
},
|
||||||
"certifi": {
|
"certifi": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:13e698f54293db9f89122b0581843a782ad0934a4fe0172d2a980ba77fc61bb7",
|
"sha256:13e698f54293db9f89122b0581843a782ad0934a4fe0172d2a980ba77fc61bb7",
|
||||||
|
@ -99,10 +108,10 @@
|
||||||
},
|
},
|
||||||
"idna": {
|
"idna": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:2c6a5de3089009e3da7c5dde64a141dbc8551d5b7f6cf4ed7c2568d0cc520a8f",
|
"sha256:156a6814fb5ac1fc6850fb002e0852d56c0c8d2531923a51032d1b70760e186e",
|
||||||
"sha256:8c7309c718f94b3a625cb648ace320157ad16ff131ae0af362c9f21b80ef6ec4"
|
"sha256:684a38a6f903c1d71d6d5fac066b58d7768af4de2b832e426ec79c30daa94a16"
|
||||||
],
|
],
|
||||||
"version": "==2.6"
|
"version": "==2.7"
|
||||||
},
|
},
|
||||||
"jdcal": {
|
"jdcal": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
|
@ -185,10 +194,10 @@
|
||||||
},
|
},
|
||||||
"requests": {
|
"requests": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:6a1b267aa90cac58ac3a765d067950e7dbbf75b1da07e895d1f594193a40a38b",
|
"sha256:63b52e3c866428a224f97cab011de738c36aec0185aa91cfacd418b5d58911d1",
|
||||||
"sha256:9c443e7324ba5b85070c4a818ade28bfabedf16ea10206da1132edaa6dda237e"
|
"sha256:ec22d826a36ed72a7358ff3fe56cbd4ba69dd7a6718ffd450ff0e9df7a47ce6a"
|
||||||
],
|
],
|
||||||
"version": "==2.18.4"
|
"version": "==2.19.1"
|
||||||
},
|
},
|
||||||
"six": {
|
"six": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
|
@ -211,10 +220,10 @@
|
||||||
},
|
},
|
||||||
"urllib3": {
|
"urllib3": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:06330f386d6e4b195fbfc736b297f58c5a892e4440e54d294d7004e3a9bbea1b",
|
"sha256:a68ac5e15e76e7e5dd2b8f94007233e01effe3e50e8daddf69acfd81cb686baf",
|
||||||
"sha256:cc44da8e1145637334317feebd728bd869a35285b93cbb4cca2577da7e62db4f"
|
"sha256:b5725a0bd4ba422ab0e66e89e030c806576753ea3ee08554382c14e685d117b5"
|
||||||
],
|
],
|
||||||
"version": "==1.22"
|
"version": "==1.23"
|
||||||
},
|
},
|
||||||
"uuid": {
|
"uuid": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
|
|
|
@ -1,7 +1,46 @@
|
||||||
document.addEventListener("DOMContentLoaded", function (event) {
|
document.addEventListener("DOMContentLoaded", function (event) {
|
||||||
console.log('document loaded');
|
var isbnButton = document.querySelector('#id_isbn_button');
|
||||||
var isbn = document.querySelector('#id_isbn');
|
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
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -27,7 +27,27 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="form-row">
|
<div class="form-row">
|
||||||
<div class="col-12">
|
<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>
|
</div>
|
||||||
<div class="form-row">
|
<div class="form-row">
|
||||||
|
@ -55,11 +75,11 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% if form.add_another %}
|
{% if form.add_another %}
|
||||||
<div class="form-row">
|
<div class="form-row">
|
||||||
<div class="col-12">
|
<div class="col-12">
|
||||||
{% bootstrap_field form.add_another %}
|
{% bootstrap_field form.add_another %}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
|
|
@ -43,8 +43,7 @@
|
||||||
|
|
||||||
{% block end_js %}
|
{% block end_js %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
<script src="https://code.jquery.com/jquery-3.3.1.slim.min.js"
|
<script src="https://code.jquery.com/jquery-3.3.1.min.js"
|
||||||
integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo"
|
|
||||||
crossorigin="anonymous"></script>
|
crossorigin="anonymous"></script>
|
||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.0/umd/popper.min.js"
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.0/umd/popper.min.js"
|
||||||
integrity="sha384-cs/chFZiN24E4KMATLdqdvsezGxaGsi4hLGOzlXwp5UZB1LY//20VyM2taTB4QvJ"
|
integrity="sha384-cs/chFZiN24E4KMATLdqdvsezGxaGsi4hLGOzlXwp5UZB1LY//20VyM2taTB4QvJ"
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
from django.urls import path
|
from django.urls import path
|
||||||
|
|
||||||
from manuels.views import AddBookView, ListBooksView, clear_teacher_view, AddSuppliesView, EditBookView, \
|
from manuels.views import AddBookView, ListBooksView, clear_teacher_view, AddSuppliesView, EditBookView, \
|
||||||
EditSuppliesView, DeleteBookView, DeleteSuppliesView, ConfirmTeacherView
|
EditSuppliesView, DeleteBookView, DeleteSuppliesView, ConfirmTeacherView, isbn_api
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path('teacher/<uuid:pk>/add_book', AddBookView.as_view(), name='add_book'),
|
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:teacher_pk>/supplies/<int:pk>/delete', DeleteSuppliesView.as_view(), name='delete_supplies'),
|
||||||
path('teacher/<uuid:pk>/confirm', ConfirmTeacherView.as_view(), name='confirm_teacher'),
|
path('teacher/<uuid:pk>/confirm', ConfirmTeacherView.as_view(), name='confirm_teacher'),
|
||||||
path('clear', clear_teacher_view, name='clear_teacher'),
|
path('clear', clear_teacher_view, name='clear_teacher'),
|
||||||
|
path('isbn_api/<str:isbn>', isbn_api, name='isbn_api'),
|
||||||
]
|
]
|
||||||
|
|
|
@ -1,6 +1,10 @@
|
||||||
|
import re
|
||||||
|
|
||||||
|
import bs4
|
||||||
|
import requests
|
||||||
from django.contrib import messages
|
from django.contrib import messages
|
||||||
from django.contrib.auth.mixins import PermissionRequiredMixin
|
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.shortcuts import get_object_or_404, redirect
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from django.views.generic import CreateView, ListView, UpdateView, DeleteView, FormView, DetailView, TemplateView
|
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)
|
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.")
|
messages.success(self.request, "Vos listes ont été validées. Votre documentaliste a été notifiée par email.")
|
||||||
return response
|
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,
|
||||||
|
})
|
||||||
|
|
Loading…
Reference in a new issue