Merge pull request #709 from Gluejar/open-edition-edit

Open edition editing
pull/43/head
eshellman 2017-10-26 13:34:10 -04:00 committed by GitHub
commit a1ab17f150
11 changed files with 83 additions and 41 deletions

View File

@ -37,7 +37,7 @@ from regluit.utils.localdatetime import now
from . import cc from . import cc
from . import models from . import models
from .parameters import WORK_IDENTIFIERS from .parameters import WORK_IDENTIFIERS
from .validation import identifier_cleaner from .validation import identifier_cleaner, unreverse_name
from .loaders.scrape import get_scraper, scrape_sitemap from .loaders.scrape import get_scraper, scrape_sitemap
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -517,6 +517,9 @@ def merge_works(w1, w2, user=None):
w2source = wishlist.work_source(w2) w2source = wishlist.work_source(w2)
wishlist.remove_work(w2) wishlist.remove_work(w2)
wishlist.add_work(w1, w2source) wishlist.add_work(w1, w2source)
for userprofile in w2.contributors.all():
userprofile.works.remove(w2)
userprofile.works.add(w1)
for identifier in w2.identifiers.all(): for identifier in w2.identifiers.all():
identifier.work = w1 identifier.work = w1
identifier.save() identifier.save()
@ -735,16 +738,6 @@ IDTABLE = [('librarything', 'ltwk'), ('goodreads', 'gdrd'), ('openlibrary', 'olw
('edition_id', 'edid'), ('googlebooks', 'goog'), ('doi', 'doi'), ('edition_id', 'edid'), ('googlebooks', 'goog'), ('doi', 'doi'),
] ]
def unreverse(name):
if not ',' in name:
return name
(last, rest) = name.split(',', 1)
if not ',' in rest:
return '%s %s' % (rest.strip(), last.strip())
(first, rest) = rest.split(',', 1)
return '%s %s, %s' % (first.strip(), last.strip(), rest.strip())
def load_from_yaml(yaml_url, test_mode=False): def load_from_yaml(yaml_url, test_mode=False):
""" """
This really should be called 'load_from_github_yaml' This really should be called 'load_from_github_yaml'
@ -877,7 +870,7 @@ class BasePandataLoader(object):
rel_code = inverse_marc_rels.get(key, 'aut') rel_code = inverse_marc_rels.get(key, 'aut')
creators = creators if isinstance(creators, list) else [creators] creators = creators if isinstance(creators, list) else [creators]
for creator in creators: for creator in creators:
edition.add_author(unreverse(creator.get('agent_name', '')), relation=rel_code) edition.add_author(unreverse_name(creator.get('agent_name', '')), relation=rel_code)
for yaml_subject in metadata.subjects: #always add yaml subjects (don't clear) for yaml_subject in metadata.subjects: #always add yaml subjects (don't clear)
if isinstance(yaml_subject, tuple): if isinstance(yaml_subject, tuple):
(authority, heading) = yaml_subject (authority, heading) = yaml_subject

View File

@ -0,0 +1,19 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('core', '0009_auto_20170808_0846'),
]
operations = [
migrations.AddField(
model_name='userprofile',
name='works',
field=models.ManyToManyField(related_name='contributors', to='core.Work', blank=True),
),
]

View File

@ -1211,6 +1211,9 @@ class UserProfile(models.Model):
librarything_id = models.CharField(max_length=31, blank=True) librarything_id = models.CharField(max_length=31, blank=True)
badges = models.ManyToManyField('Badge', related_name='holders', blank=True) badges = models.ManyToManyField('Badge', related_name='holders', blank=True)
kindle_email = models.EmailField(max_length=254, blank=True) kindle_email = models.EmailField(max_length=254, blank=True)
# keep track of work the user adds
works = models.ManyToManyField('Work', related_name='contributors', blank=True)
goodreads_user_id = models.CharField(max_length=32, null=True, blank=True) goodreads_user_id = models.CharField(max_length=32, null=True, blank=True)
goodreads_user_name = models.CharField(max_length=200, null=True, blank=True) goodreads_user_name = models.CharField(max_length=200, null=True, blank=True)

View File

@ -129,6 +129,17 @@ def valid_subject( subject_name ):
return False return False
return True return True
reverse_name_comma = re.compile(r',(?! *Jr[\., ])')
def unreverse_name(name):
if not reverse_name_comma.search(name):
return name
(last, rest) = name.split(',', 1)
if not ',' in rest:
return '%s %s' % (rest.strip(), last.strip())
(first, rest) = rest.split(',', 1)
return '%s %s, %s' % (first.strip(), last.strip(), rest.strip())
def authlist_cleaner(authlist): def authlist_cleaner(authlist):
''' given a author string or list of author strings, checks that the author string ''' given a author string or list of author strings, checks that the author string
is not a list of author names and that no author is repeated''' is not a list of author names and that no author is repeated'''
@ -144,16 +155,18 @@ def authlist_cleaner(authlist):
# Match comma but not ", Jr" # Match comma but not ", Jr"
comma_list_delim = re.compile(r',(?! *Jr[\., ])') comma_list_delim = re.compile(r',(?! *Jr[\., ])')
spaces = re.compile(r'\s+') spaces = re.compile(r'\s+')
_and_ = re.compile(r',? and ') _and_ = re.compile(r',? (and|\&) ')
semicolon_list_delim = re.compile(r'[\;|\&]')
def auth_cleaner(auth): def auth_cleaner(auth):
''' given a author string checks that the author string ''' given a author string checks that the author string
is not a list of author names''' is not a list of author names'''
cleaned = [] cleaned = []
auth = _and_.sub(',', auth)
if ';' in auth: if ';' in auth:
authlist = auth.split(';') authlist = semicolon_list_delim.split(auth)
authlist = [unreverse_name(name) for name in authlist]
else: else:
auth = _and_.sub(',', auth)
authlist = comma_list_delim.split(auth) authlist = comma_list_delim.split(auth)
for auth in authlist: for auth in authlist:
cleaned.append(spaces.sub(' ', auth.strip())) cleaned.append(spaces.sub(' ', auth.strip()))

View File

@ -51,7 +51,7 @@ class IdentifierForm(forms.ModelForm):
required=False, required=False,
) )
make_new = forms.BooleanField( make_new = forms.BooleanField(
label='There\'s no existing Identifier, so please use an Unglue.it ID', label='There\'s no existing Identifier. ',
required=False, required=False,
) )
identifier = None identifier = None
@ -61,7 +61,7 @@ class IdentifierForm(forms.ModelForm):
id_value = self.cleaned_data.get('id_value', '').strip() id_value = self.cleaned_data.get('id_value', '').strip()
make_new = self.cleaned_data.get('make_new', False) make_new = self.cleaned_data.get('make_new', False)
if not make_new: if not make_new:
self.cleaned_data['value'] = identifier_cleaner(id_type)(id_value) self.cleaned_data['id_value'] = identifier_cleaner(id_type)(id_value)
return self.cleaned_data return self.cleaned_data
class Meta: class Meta:

View File

@ -238,7 +238,12 @@ ul.fancytree-container {
{% else %} {% else %}
{% if edition.work %} {% if edition.work %}
<h2><a href="{% url 'work' edition.work.id %}">{{ edition.work.title }}</a> was added to Unglue.it on {{ edition.work.created }}</h2>
{% include 'edition_display.html' %} {% include 'edition_display.html' %}
<div class="launch_top pale">
Are you the author or other rightsholder for this work?
To edit the metadata or add editions, become an Unglue.it <a href="{% url 'rightsholders' %}">rights holder</a>.
</div>
{% else %} {% else %}
Sorry, there's no work specified. Sorry, there's no work specified.
{% endif %} {% endif %}

View File

@ -39,11 +39,7 @@
web: <a href="{{ edition.http_id }}">{{ edition.http_id }}</a><br /> web: <a href="{{ edition.http_id }}">{{ edition.http_id }}</a><br />
{% endif %} {% endif %}
{% if not managing %} {% if not managing %}
{% if user.is_staff %} {% if user_can_edit_work %}
<a href="{% url 'new_edition' work_id edition.id %}">Edit this edition</a><br />
{% elif user in work.last_campaign.managers.all %}
<a href="{% url 'new_edition' work_id edition.id %}">Edit this edition</a><br />
{% elif user.rights_holder.count and not work.last_campaign %}
<a href="{% url 'new_edition' work_id edition.id %}">Edit this edition</a><br /> <a href="{% url 'new_edition' work_id edition.id %}">Edit this edition</a><br />
{% endif %} {% endif %}
{% if user.is_authenticated %} {% if user.is_authenticated %}

View File

@ -18,6 +18,9 @@
<p>To add a work and edition to Unglue.it, we need to start with an identifier. We'll see if we can find some metadata based on the identifier you give us. If you give us an ISBN, we'll often be able to find some. If there's no ISBN, give us a URL (a web address) and we'll check the page for bibliographic info. </p> <p>To add a work and edition to Unglue.it, we need to start with an identifier. We'll see if we can find some metadata based on the identifier you give us. If you give us an ISBN, we'll often be able to find some. If there's no ISBN, give us a URL (a web address) and we'll check the page for bibliographic info. </p>
{% if not request.user.rights_holder.all.count %}
<p>If someone else has already added the work to unglue.it, you may not be able to edit its metadata.</p>
{% endif %}
<form id="editform" enctype="multipart/form-data" method="POST" action="#"> <form id="editform" enctype="multipart/form-data" method="POST" action="#">
{% csrf_token %} {% csrf_token %}
{{ form.as_p }} {{ form.as_p }}

View File

@ -476,7 +476,7 @@
<ul id="kw_list"></ul> <ul id="kw_list"></ul>
{% endif %} {% endif %}
{% if user.is_staff or user in work.last_campaign.managers.all %} {% if user_can_edit_work %}
<form method="POST" id="kw_add_form">{% csrf_token %} <form method="POST" id="kw_add_form">{% csrf_token %}
{{ kwform.add_kw }}<input type="hidden" name="kw_add" value="true"> <input type="submit" name="kw_add_fake" value="add keyword" id="kw_add_form_submit" /> {{ kwform.add_kw }}<input type="hidden" name="kw_add" value="true"> <input type="submit" name="kw_add_fake" value="add keyword" id="kw_add_form_submit" />
</form> </form>
@ -486,7 +486,7 @@
{% if alert %} {% if alert %}
<div class="yikes"><br />{{ alert }}</div> <div class="yikes"><br />{{ alert }}</div>
{% endif %} {% endif %}
{% if user.is_staff or user in work.last_campaign.managers.all %} {% if user_can_edit_work %}
<div><a href="{% url 'new_edition' work_id edition.id %}">Create a new edition for this work</a><br /><br /></div> <div><a href="{% url 'new_edition' work_id edition.id %}">Create a new edition for this work</a><br /><br /></div>
{% endif %} {% endif %}

View File

@ -397,6 +397,7 @@ def work(request, work_id, action='display'):
return render(request, 'work.html', { return render(request, 'work.html', {
'work': work, 'work': work,
'user_can_edit_work': user_can_edit_work(request.user, work),
'premiums': premiums, 'premiums': premiums,
'ungluers': userlists.supporting_users(work, 5), 'ungluers': userlists.supporting_users(work, 5),
'claimform': claimform, 'claimform': claimform,
@ -642,6 +643,8 @@ def googlebooks(request, googlebooks_id):
if edition.new: if edition.new:
# add related editions asynchronously # add related editions asynchronously
tasks.populate_edition.delay(edition.isbn_13) tasks.populate_edition.delay(edition.isbn_13)
if request.user.is_authenticated():
request.user.profile.works.add(edition.work)
except bookloader.LookupFailure: except bookloader.LookupFailure:
logger.warning("failed to load googlebooks_id %s" % googlebooks_id) logger.warning("failed to load googlebooks_id %s" % googlebooks_id)
return HttpResponseNotFound("failed looking up googlebooks id %s" % googlebooks_id) return HttpResponseNotFound("failed looking up googlebooks id %s" % googlebooks_id)

View File

@ -29,15 +29,19 @@ def user_can_edit_work(user, work):
''' '''
Check if a user is allowed to edit the work Check if a user is allowed to edit the work
''' '''
if user.is_staff : if user.is_anonymous():
return False
elif user.is_staff :
return True return True
elif work and work.last_campaign(): elif work and work.last_campaign():
return user in work.last_campaign().managers.all() return user in work.last_campaign().managers.all()
elif user.rights_holder.count() and (work == None or not work.last_campaign()): elif user.rights_holder.count() and (work == None or not work.last_campaign()):
# allow rights holders to edit unless there is a campaign # allow rights holders to edit unless there is a campaign
return True return True
elif work and work.claim.all():
return True if work.claim.filter(user=user) else False
else: else:
return False return user.profile in work.contributors.all()
def safe_get_work(work_id): def safe_get_work(work_id):
""" """
@ -68,6 +72,11 @@ def get_edition(edition_id):
except models.Edition.DoesNotExist: except models.Edition.DoesNotExist:
raise Http404 (duplicate-code) raise Http404 (duplicate-code)
def user_edition(edition, user):
if user and user.is_authenticated() and edition:
user.profile.works.add(edition.work)
return edition
def get_edition_for_id(id_type, id_value, user=None): def get_edition_for_id(id_type, id_value, user=None):
''' the identifier is assumed to not be in database ''' ''' the identifier is assumed to not be in database '''
identifiers = {id_type: id_value} identifiers = {id_type: id_value}
@ -87,17 +96,18 @@ def get_edition_for_id(id_type, id_value, user=None):
if identifiers.has_key('goog'): if identifiers.has_key('goog'):
edition = add_by_googlebooks_id(identifiers['goog']) edition = add_by_googlebooks_id(identifiers['goog'])
if edition: if edition:
return edition
return user_edition(edition, user)
if identifiers.has_key('isbn'): if identifiers.has_key('isbn'):
edition = add_by_isbn(identifiers['isbn']) edition = add_by_isbn(identifiers['isbn'])
if edition: if edition:
return edition return user_edition(edition, user)
if identifiers.has_key('oclc'): if identifiers.has_key('oclc'):
edition = add_by_oclc(identifiers['oclc']) edition = add_by_oclc(identifiers['oclc'])
if edition: if edition:
return edition return user_edition(edition, user)
if identifiers.has_key('glue'): if identifiers.has_key('glue'):
try: try:
@ -108,7 +118,7 @@ def get_edition_for_id(id_type, id_value, user=None):
if identifiers.has_key('http'): if identifiers.has_key('http'):
edition = add_by_webpage(identifiers['http'], user=user) edition = add_by_webpage(identifiers['http'], user=user)
return edition return user_edition(edition, user)
# return a dummy edition and identifier # return a dummy edition and identifier
@ -126,7 +136,7 @@ def get_edition_for_id(id_type, id_value, user=None):
models.Identifier.objects.create(type=key, value=id_value, work=work, edition=None) models.Identifier.objects.create(type=key, value=id_value, work=work, edition=None)
else: else:
models.Identifier.objects.create(type=key, value=id_value, work=work, edition=edition) models.Identifier.objects.create(type=key, value=id_value, work=work, edition=edition)
return edition return user_edition(edition, user)
@login_required @login_required
def new_edition(request, by=None): def new_edition(request, by=None):
@ -138,7 +148,7 @@ def new_edition(request, by=None):
form = IdentifierForm(data=request.POST) form = IdentifierForm(data=request.POST)
if form.is_valid(): if form.is_valid():
if form.cleaned_data.get('make_new', False): if form.cleaned_data.get('make_new', False):
edition = get_edition_for_id('glue', 'new') edition = get_edition_for_id('glue', 'new', user=request.user)
else: else:
id_type = form.cleaned_data['id_type'] id_type = form.cleaned_data['id_type']
id_value = form.cleaned_data['id_value'] id_value = form.cleaned_data['id_value']
@ -238,17 +248,14 @@ def edit_edition(request, work_id, edition_id, by=None):
ebookchange = True ebookchange = True
if ebookchange: if ebookchange:
form = EditionForm(instance=edition, data=request.POST, files=request.FILES) form = EditionForm(instance=edition, data=request.POST, files=request.FILES)
if request.POST.has_key('add_author_submit') and admin:
if request.POST.get('add_author', None) and admin:
new_author_name = request.POST['add_author'].strip() new_author_name = request.POST['add_author'].strip()
new_author_relation = request.POST['add_author_relation'] new_author_relation = request.POST['add_author_relation']
try: if (new_author_name, new_author_relation) not in edition.new_authors:
author = models.Author.objects.get(name=new_author_name) edition.new_authors.append((new_author_name, new_author_relation))
except models.Author.DoesNotExist: form = EditionForm(instance=edition, data=request.POST, files=request.FILES)
author = models.Author.objects.create(name=new_author_name) if not request.POST.has_key('add_author_submit') and admin:
edition.new_authors.append((new_author_name, new_author_relation))
form = EditionForm(instance=edition, data=request.POST, files=request.FILES)
elif not form and admin:
form = EditionForm(instance=edition, data=request.POST, files=request.FILES)
if form.is_valid(): if form.is_valid():
form.save() form.save()
if not work: if not work: