Skip to content
This repository was archived by the owner on Jun 1, 2022. It is now read-only.

Commit 80ea268

Browse files
committed
Merge pull request #207 from editorsnotes/fix-editing-bugs
Fix (MORE) editing bugs...
2 parents fe40b3c + 0ffa874 commit 80ea268

File tree

17 files changed

+255
-55
lines changed

17 files changed

+255
-55
lines changed

editorsnotes/admin_custom/forms/common.py

Lines changed: 34 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -8,20 +8,37 @@
88

99
from editorsnotes.main.models.topics import Topic, TopicAssignment
1010

11-
#class TopicAssignmentWidget(forms.widgets.HiddenInput):
12-
# def render(self, name, value, attrs=None):
13-
# hidden_input = super(TopicAssignmentWidget, self).render(name, value, attrs=None)
14-
# if value:
15-
# topic = get_object_or_404(ProjectTopicContainer, id=value)
16-
# extra_content = topic.preferred_name
17-
# else:
18-
# extra_content = ''
19-
# return mark_safe(extra_content + hidden_input)
20-
#
21-
#class TopicAssignmentForm(ModelForm):
22-
# class Meta:
23-
# model = TopicNodeAssignment
24-
# widgets = {'topic': TopicNodeAssignmentWidget()}
25-
#
26-
#TopicAssignmentFormset = generic_inlineformset_factory(
27-
# TopicNodeAssignment, form=TopicNodeAssignmentForm, extra=1)
11+
class TopicAssignmentWidget(forms.widgets.HiddenInput):
12+
def render(self, name, value, attrs=None):
13+
hidden_input = super(TopicAssignmentWidget, self).render(name, value, attrs=None)
14+
if value:
15+
topic = get_object_or_404(Topic, id=value)
16+
extra_content = topic.preferred_name
17+
else:
18+
extra_content = ''
19+
return mark_safe(extra_content + hidden_input)
20+
21+
class TopicAssignmentForm(ModelForm):
22+
topic_id = forms.IntegerField(widget=TopicAssignmentWidget)
23+
class Meta:
24+
model = TopicAssignment
25+
fields = ('topic_id',)
26+
def __init__(self, *args, **kwargs):
27+
super(TopicAssignmentForm, self).__init__(*args, **kwargs)
28+
if self.instance and self.instance.id:
29+
self.fields['topic_id'].initial = self.instance.topic.id
30+
def clean_topic_id(self):
31+
data = self.cleaned_data['topic_id']
32+
if self.instance and self.instance.id:
33+
return data
34+
# If this is a new object, the "topic_id" is actually the
35+
# "topic_node_id". This is hacky and will be removed when we eventually
36+
# create interfaces for editing anything with the API
37+
topic_qs = Topic.objects.filter(topic_node_id=data)
38+
if not topic_qs.exists():
39+
raise forms.ValidationError('This topic does not exist.')
40+
self.cleaned_data['topic_qs'] = topic_qs
41+
return data
42+
43+
TopicAssignmentFormset = generic_inlineformset_factory(
44+
TopicAssignment, form=TopicAssignmentForm, extra=1)

editorsnotes/admin_custom/forms/documents.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -40,9 +40,10 @@ def save_zotero_data(self):
4040
elif self.changed_data is not None and 'zotero_string' in self.changed_data:
4141
document.zotero_data = self.cleaned_data['zotero_string']
4242
document.save()
43-
link, created = ZoteroLink.objects.get_or_create(zotero_item=document)
44-
if not created:
45-
link.save()
43+
if not document.zotero_link:
44+
ZoteroLink.objects.create(zotero_item=document)
45+
else:
46+
document.zotero_link.save()
4647
return document
4748

4849
class DocumentLinkForm(ModelForm):

editorsnotes/admin_custom/forms/topics.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ class TopicForm(models.ModelForm):
1212
class Media:
1313
js = (
1414
"function/admin-bootstrap-topic.js",
15-
"function/admin-bootstrap-note-sections.js",
1615
)
1716
class Meta:
1817
model = Topic
@@ -28,7 +27,10 @@ class CitationForm(models.ModelForm):
2827
class Meta:
2928
model = Citation
3029
fields = ('document', 'notes', 'ordering',)
31-
widgets = {'document': forms.widgets.HiddenInput()}
30+
widgets = {
31+
'document': forms.widgets.HiddenInput(),
32+
'ordering': forms.widgets.HiddenInput()
33+
}
3234

3335
CitationFormset = generic_inlineformset_factory(
3436
Citation, form=CitationForm, extra=1)

editorsnotes/admin_custom/static/function/admin-bootstrap-base.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -208,14 +208,14 @@ $(document).ready(function () {
208208
.autocomplete({
209209
source: function (request, response) {
210210
$.ajax({
211-
url: '/api/topics/',
211+
url: '/api' + window.location.pathname.match(/^\/projects\/[^/]+/)[0] + '/topics/',
212212
dataType: 'json',
213213
data: {'q': request.term},
214214
beforeSend: function () {
215215
$('#related-topics-loading').css('display', 'inline-block');
216216
},
217217
success: function (data) {
218-
response($.map(data, function (item, index) {
218+
response($.map(data.results, function (item, index) {
219219
return { id: item.id, label: item.preferred_name, uri: item.uri };
220220
}));
221221
},
@@ -262,7 +262,7 @@ $(document).ready(function () {
262262
// Replace this input with a text field
263263
$oldExtraField
264264
.prepend(ui.item.label)
265-
.find('input[name$=topic]').val(ui.item.id);
265+
.find('input[name$="topic_id"]').val(ui.item.id);
266266

267267
// Clear this input
268268
event.preventDefault();
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,94 @@
1+
function bindCitation($citation) {
2+
var $input = $citation.find('.citation-document input')
3+
, prefix = 'id_' + $citation.prop('id')
4+
, autocompleteopts
5+
, editor = new wysihtml5.Editor(prefix + '-notes', {
6+
toolbar: $citation.prop('id') + '-toolbar',
7+
parserRules: wysihtml5ParserRules,
8+
useLineBreaks: false,
9+
stylesheets: ['/static/function/wysihtml5/stylesheet.css']
10+
});
11+
12+
$('#' + prefix + '-toolbar').show();
13+
14+
if ($input.length) {
15+
autocompleteopts= $.extend($('body').data('baseAutocompleteOptions'), {
16+
select: function (e, ui) {
17+
if (!ui.item) return;
18+
$citation.find('.citation-document').addClass('document-selected');
19+
$input.replaceWith(ui.item.value);
20+
$citation.find('#' + prefix + '-document').val(ui.item.id);
21+
}
22+
});
23+
24+
$input.autocomplete(autocompleteopts)
25+
.data('ui-autocomplete')._renderItem = function (ul, item) {
26+
return $('<li>')
27+
.data('ui-autocomplete-item', item)
28+
.append('<a>' + item.value + '</a>')
29+
.appendTo(ul)
30+
}
31+
}
32+
33+
34+
35+
}
36+
37+
function citationFormset($el) {
38+
var lastForm;
39+
40+
this.$el = $el;
41+
this.$items = $el.find('#citation-items');
42+
this.managementForm = $el.find('#id_citation-TOTAL_FORMS');
43+
this.lastForm = this.$items.children().last();
44+
this.blankForm = this.lastForm.clone();
45+
this.counter = parseInt(this.managementForm.val(), 10);
46+
47+
this.$items.children().each(function (idx, el) {
48+
bindCitation($(el));
49+
});
50+
51+
return this;
52+
}
53+
54+
citationFormset.prototype.addCitation = function () {
55+
var that = this
56+
, newForm
57+
, searchstr
58+
59+
if (this.lastForm.is(':hidden')) {
60+
this.lastForm.removeClass('hide');
61+
return
62+
}
63+
64+
newForm = this.blankForm.clone().removeClass('hide');
65+
searchstr = 'citation-' + (this.counter - 1) + '-'
66+
67+
newForm.prop('id', 'citation-' + that.counter);
68+
newForm.find('[name^="' + searchstr + '"], [id^="' + searchstr + '"]')
69+
.each(function (idx, el) {
70+
if (el.name) el.name = el.name.replace('-' + (that.counter - 1) + '-', '-' + that.counter + '-');
71+
if (el.id) el.id = el.id.replace('-' + (that.counter - 1) + '-', '-' + that.counter + '-');
72+
});
73+
74+
newForm.appendTo(that.$items);
75+
bindCitation(newForm);
76+
77+
this.counter += 1;
78+
this.managementForm.val(that.counter);
79+
}
80+
81+
182
$(document).ready(function() {
283
var editor = new wysihtml5.Editor('id_summary', {
384
toolbar: 'summary-toolbar',
485
parserRules: wysihtml5ParserRules,
586
useLineBreaks: false,
687
stylesheets: ['/static/function/wysihtml5/stylesheet.css']
788
});
89+
90+
var formset = new citationFormset($('#citations-formset'));
91+
92+
$('#add-citation').on('click', formset.addCitation.bind(formset));
93+
894
});

editorsnotes/admin_custom/static/style/bootstrap-admin.css

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,29 @@
7676
display: none;
7777
}
7878

79+
/* Hide delete field for citations */
80+
.citation input[id$="-DELETE"] {
81+
display: none;
82+
}
83+
84+
/* Topic citation spacing (DELETE ME GOD DELETE ME WHEN YOU CAN) */
85+
.citation-document i {
86+
font-size: 24px;
87+
position: relative;
88+
top: 4px;
89+
margin-right: 4px;
90+
}
91+
.citation-document input {
92+
width: 750px;
93+
}
94+
95+
.citation-document.document-selected * {
96+
display: inline-block;
97+
}
98+
#citations-formset .citation {
99+
margin: 1em 0 2em;
100+
}
101+
79102

80103
/* Transcript editor should be large */
81104
fieldset[name="transcript_content"] textarea {

editorsnotes/admin_custom/templates/includes/citation_formset.html

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,18 @@
88
{% for citation in citations_formset %}
99
{% with citation.instance as c %}
1010

11-
<div class="citation citation-edit {% if forloop.last %}hide{% endif %}" id="{{ citation.prefix }}">
11+
<div class="citation citation-edit {% if not c %}hide{% endif %}" id="{{ citation.prefix }}">
1212

1313
<div class="citation-document">
1414
<i class="icon-file"></i>
15-
{% if c.document %} {{ c.document.description|as_text }} {% endif %}
15+
{% if c.document %}
16+
{{ c.document.description|as_text }}
17+
{% else %}
18+
<input data-target-model="documents" data-project-slug="{{ project.slug }}" />
19+
{% endif %}
1620
</div>
1721

18-
<div class="citation-notes">{% if c.has_notes %}{{ c.notes|as_html }}{% endif %}</div>
19-
22+
{% include "includes/wysihtml5_full_toolbar.html" with toolbar_id=citation.prefix|add:"-toolbar" %}
2023
<div class="citation-fields">
2124
{% for field in citation %} {{ field }} {% endfor %}
2225
</div>
@@ -28,7 +31,6 @@
2831

2932
</div>
3033

31-
{% include "includes/wysihtml5_full_toolbar.html" with toolbar_id="citation-toolbar" %}
3234
{% include "includes/add_document_modal.html" %}
3335

3436
</div>

editorsnotes/admin_custom/templates/topic_admin.html

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -56,14 +56,16 @@ <h3>{% if form.instance.id %}Change {% else %}Add {% endif %}topic</h3>
5656
</div>
5757

5858
<div class="article-citations" style="margin: 1em .5em;">
59-
<h4>Citations</h4>
59+
<legend>Citations</legend>
6060
{% include "includes/citation_formset.html" with citations_formset=formsets.citation %}
61-
<a href="#" id="add-citation">Add citation</a>
61+
<button type="button" class="btn btn-primary" id="add-citation">Add citation</a>
6262
</div>
6363

6464
</fieldset>
6565

66-
<button type="submit" class="btn btn-primary">Save</button>
66+
<hr />
67+
68+
<button type="submit" class="btn">Save</button>
6769

6870
</form>
6971
{% endblock %}

editorsnotes/admin_custom/tests.py

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,11 @@
99

1010
class TopicAdminTestCase(WebTest):
1111
fixtures = ['projects.json']
12+
def create_test_topic(self, name=u'Emma Goldman'):
13+
project = main_models.Project.objects.get(slug='emma');
14+
node, topic = main_models.Topic.objects.create_along_with_node(
15+
name, project, project.members.get())
16+
return topic
1217
def test_create_topic(self):
1318
"A user should be able to create a topic with a unique name."
1419
topic_name = u'Emma Goldman'
@@ -33,6 +38,63 @@ def test_create_topic(self):
3338
self.assertEqual(error_messages[0].text.strip(),
3439
u'Topic with this preferred name already exists.')
3540

41+
def test_add_citation(self):
42+
topic = self.create_test_topic()
43+
document = main_models.Document.objects.create(
44+
description="a test document",
45+
project_id=topic.project_id,
46+
creator_id=topic.creator_id,
47+
last_updater_id=topic.last_updater_id)
48+
url = reverse('admin:main_topic_change',
49+
kwargs={'project_slug': 'emma', 'topic_node_id': topic.topic_node_id})
50+
51+
form = self.app.get(url, user='barry').forms[1]
52+
form['citation-0-document'] = document.id
53+
form['citation-0-notes'] = 'it was an interesting thing I found in here'
54+
55+
resp = form.submit().follow()
56+
topic = main_models.Topic.objects.get(id=topic.id)
57+
self.assertEqual(topic.summary_cites.count(), 1)
58+
self.assertEqual(topic.summary_cites.get().document_id, document.id)
59+
self.assertEqual(topic.summary_cites.get().object_id, topic.id)
60+
61+
def test_add_related_topics(self):
62+
topic = self.create_test_topic()
63+
64+
# This is to make sure topic id counter and topic node id counter are
65+
# off. hope you can handle that
66+
main_models.TopicNode.objects.create(_preferred_name='dummy',
67+
creator_id=1, last_updater_id=1)
68+
69+
topic_2 = self.create_test_topic(name=u'Alexander Berkman')
70+
topic_3 = self.create_test_topic(name=u'Ben Reitman')
71+
72+
url = reverse('admin:main_topic_change',
73+
kwargs={'project_slug': 'emma', 'topic_node_id': topic.topic_node_id})
74+
75+
# Add one topic
76+
form = self.app.get(url, user='barry').forms[1]
77+
form['topicassignment-0-topic_id'] = topic_2.topic_node_id
78+
resp = form.submit().follow()
79+
self.assertEqual(topic.related_topics.count(), 1)
80+
self.assertEqual(topic.related_topics.get().topic_id, topic_2.id)
81+
82+
# Add another topic
83+
form = self.app.get(url, user='barry').forms[1]
84+
form['topicassignment-1-topic_id'] = topic_3.topic_node_id
85+
resp = form.submit().follow()
86+
self.assertEqual(topic.related_topics.count(), 2)
87+
self.assertEqual(sorted([topic_2.id, topic_3.id]),
88+
sorted(topic.related_topics.values_list('topic_id', flat=True)))
89+
90+
# Delete both topics
91+
form = self.app.get(url, user='barry').forms[1]
92+
form['topicassignment-0-DELETE'] = 'true'
93+
form['topicassignment-1-DELETE'] = 'true'
94+
resp = form.submit().follow()
95+
self.assertEqual(topic.related_topics.count(), 0)
96+
97+
3698
def test_create_empty_topic_error(self):
3799
"A user should not be able to create a topic with an empty name."
38100
add_url = reverse('admin:main_topic_add', kwargs={'project_slug': 'emma'})

editorsnotes/admin_custom/views/common.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
from django.views.generic.edit import View, ModelFormMixin, TemplateResponseMixin
77
import reversion
88

9-
from editorsnotes.main.models import Project
9+
from editorsnotes.main.models import Project, Topic
1010
from editorsnotes.main.models.auth import RevisionProject
1111

1212
VIEW_ERROR_MSG = 'You do not have permission to view {}.'
@@ -159,15 +159,15 @@ def form_invalid(self, form, formsets):
159159
self.get_context_data(form=form, formsets=formsets))
160160

161161
def save_topicassignment_formset_form(self, form):
162-
if not form.cleaned_data['topic']:
162+
if not form.cleaned_data['topic_qs']:
163163
return
164164
if form.instance and form.instance.id:
165165
return
166-
if form.cleaned_data['topic'] in self.object.related_topics.all():
167-
return
168166
ta = form.save(commit=False)
167+
ta.topic = form.cleaned_data['topic_qs'].get(project__slug=self.project.slug)
168+
if ta.topic in self.object.related_topics.all():
169+
return
169170
ta.creator = self.request.user
170-
ta.topic = form.cleaned_data['topic']
171171
ta.content_object = self.object
172172
ta.save()
173173
return ta

0 commit comments

Comments
 (0)