0002-cards-create-related-card-in-a-popup-48534.patch
tests/backoffice_pages/test_carddata.py | ||
---|---|---|
654 | 654 |
assert 'Associated User' in resp |
655 | 655 |
assert carddef.data_class().get(carddata.id).user_id == str(user.id) |
656 | 656 |
assert '/user-pending-forms' not in resp.text |
657 | ||
658 | ||
659 |
def test_carddata_add_related(pub): |
|
660 |
user = create_user(pub) |
|
661 | ||
662 |
BlockDef.wipe() |
|
663 |
block = BlockDef() |
|
664 |
block.name = 'child' |
|
665 |
block.fields = [ |
|
666 |
fields.ItemField( |
|
667 |
id='1', |
|
668 |
label='Child', |
|
669 |
type='item', |
|
670 |
data_source={'type': 'carddef:child'}, |
|
671 |
display_mode='autocomplete', |
|
672 |
), |
|
673 |
] |
|
674 |
block.store() |
|
675 | ||
676 |
CardDef.wipe() |
|
677 |
family = CardDef() |
|
678 |
family.name = 'Family' |
|
679 |
family.fields = [ |
|
680 |
fields.ItemField( |
|
681 |
id='1', |
|
682 |
label='RL1', |
|
683 |
type='item', |
|
684 |
data_source={'type': 'carddef:adult'}, |
|
685 |
display_mode='autocomplete', |
|
686 |
), |
|
687 |
fields.ItemField( |
|
688 |
id='2', |
|
689 |
label='RL2', |
|
690 |
type='item', |
|
691 |
data_source={'type': 'carddef:adult'}, |
|
692 |
display_mode='autocomplete', |
|
693 |
), |
|
694 |
fields.BlockField(id='3', label='Children', type='block:child', max_items=42), |
|
695 |
] |
|
696 |
family.backoffice_submission_roles = user.roles |
|
697 |
family.workflow_roles = {'_editor': user.roles[0]} |
|
698 |
family.store() |
|
699 |
family.data_class().wipe() |
|
700 | ||
701 |
adult = CardDef() |
|
702 |
adult.name = 'Adult' |
|
703 |
adult.fields = [ |
|
704 |
fields.ItemField( |
|
705 |
id='1', |
|
706 |
label='First name', |
|
707 |
type='string', |
|
708 |
), |
|
709 |
fields.ItemField( |
|
710 |
id='2', |
|
711 |
label='Last name', |
|
712 |
type='string', |
|
713 |
), |
|
714 |
] |
|
715 |
adult.backoffice_submission_roles = user.roles |
|
716 |
adult.workflow_roles = {'_editor': user.roles[0]} |
|
717 |
adult.store() |
|
718 |
adult.data_class().wipe() |
|
719 | ||
720 |
child = CardDef() |
|
721 |
child.name = 'Child' |
|
722 |
child.fields = [ |
|
723 |
fields.ItemField( |
|
724 |
id='1', |
|
725 |
label='First name', |
|
726 |
type='string', |
|
727 |
), |
|
728 |
fields.ItemField( |
|
729 |
id='2', |
|
730 |
label='Last name', |
|
731 |
type='string', |
|
732 |
), |
|
733 |
] |
|
734 |
child.backoffice_submission_roles = user.roles |
|
735 |
child.workflow_roles = {'_editor': user.roles[0]} |
|
736 |
child.store() |
|
737 |
child.data_class().wipe() |
|
738 | ||
739 |
app = login(get_app(pub)) |
|
740 |
resp = app.get('/backoffice/data/family/add/') |
|
741 |
assert 'Add another RL1' in resp |
|
742 |
assert 'Add another RL2' in resp |
|
743 |
assert 'Add another Child' in resp |
|
744 |
assert resp.text.count('/backoffice/data/adult/add/?_popup=1') == 2 |
|
745 |
assert '/backoffice/data/child/add/?_popup=1' in resp |
|
746 | ||
747 |
# no autocompletion for RL1 |
|
748 |
family.fields[0].display_mode = [] |
|
749 |
family.store() |
|
750 |
resp = app.get('/backoffice/data/family/add/') |
|
751 |
assert 'Add another RL1' not in resp |
|
752 |
assert 'Add another RL2' in resp |
|
753 |
assert 'Add another Child' in resp |
|
754 |
assert resp.text.count('/backoffice/data/adult/add/?_popup=1') == 1 |
|
755 |
assert '/backoffice/data/child/add/?_popup=1' in resp |
|
756 | ||
757 |
# user ha no creation rights on child |
|
758 |
child.backoffice_submission_roles = None |
|
759 |
child.store() |
|
760 |
resp = app.get('/backoffice/data/family/add/') |
|
761 |
assert 'Add another RL1' not in resp |
|
762 |
assert 'Add another RL2' in resp |
|
763 |
assert 'Add another Child' not in resp |
|
764 |
assert resp.text.count('/backoffice/data/adult/add/?_popup=1') == 1 |
|
765 |
assert '/backoffice/data/child/add/?_popup=1' not in resp |
wcs/api.py | ||
---|---|---|
286 | 286 |
json_input = get_request().json |
287 | 287 |
formdata = self.formdef.data_class()() |
288 | 288 | |
289 |
if not (user and self.can_user_add_cards()):
|
|
289 |
if not (user and self.formdef.can_user_add_cards(user)):
|
|
290 | 290 |
raise AccessForbiddenError('cannot create card') |
291 | 291 | |
292 | 292 |
if 'data' in json_input: |
... | ... | |
343 | 343 |
if get_request().get_method() != 'PUT': |
344 | 344 |
raise MethodNotAllowedError(allowed_methods=['PUT']) |
345 | 345 |
get_request()._user = get_user_from_api_query_string() |
346 |
if not (get_request()._user and self.can_user_add_cards()):
|
|
346 |
if not (get_request()._user and self.formdef.can_user_add_cards(get_request()._user)):
|
|
347 | 347 |
raise AccessForbiddenError('cannot import cards') |
348 | 348 | |
349 | 349 |
afterjob = bool(get_request().form.get('async') == 'on') |
wcs/backoffice/data_management.py | ||
---|---|---|
17 | 17 |
import csv |
18 | 18 |
import datetime |
19 | 19 |
import io |
20 |
import json |
|
20 | 21 | |
21 |
from django.utils.encoding import force_text |
|
22 | 22 |
from quixote import get_publisher |
23 | 23 |
from quixote import get_request |
24 | 24 |
from quixote import get_response |
25 | 25 |
from quixote import redirect |
26 |
from quixote.html import TemplateIO |
|
27 |
from quixote.html import htmlescape |
|
28 | 26 |
from quixote.html import htmltext |
29 | 27 | |
30 | 28 |
from wcs import fields |
... | ... | |
116 | 114 |
def add(self): |
117 | 115 |
return CardFillPage(self.formdef.url_name) |
118 | 116 | |
119 |
def can_user_add_cards(self): |
|
120 |
if not self.formdef.backoffice_submission_roles: |
|
121 |
return False |
|
122 |
for role in get_request().user.get_roles(): |
|
123 |
if role in self.formdef.backoffice_submission_roles: |
|
124 |
return True |
|
125 |
return False |
|
126 | ||
127 | 117 |
def listing_top_actions(self): |
128 |
if not self.can_user_add_cards():
|
|
118 |
if not self.formdef.can_user_add_cards(get_request().user):
|
|
129 | 119 |
return '' |
130 | 120 |
return htmltext('<span class="actions"><a href="./add/">%s</a></span>') % _('Add') |
131 | 121 | |
... | ... | |
149 | 139 | |
150 | 140 |
def get_formdata_sidebar_actions(self, qs=''): |
151 | 141 |
r = super(CardPage, self).get_formdata_sidebar_actions(qs=qs) |
152 |
if self.can_user_add_cards():
|
|
142 |
if self.formdef.can_user_add_cards(get_request().user):
|
|
153 | 143 |
r += htmltext('<li><a rel="popup" href="import-csv">%s</a></li>') % _( |
154 | 144 |
'Import data from a CSV file' |
155 | 145 |
) |
... | ... | |
204 | 194 |
return output.getvalue() |
205 | 195 | |
206 | 196 |
def import_csv(self): |
207 |
if not self.can_user_add_cards():
|
|
197 |
if not self.formdef.can_user_add_cards(get_request().user):
|
|
208 | 198 |
raise errors.AccessForbiddenError() |
209 | 199 |
context = {'required_fields': []} |
210 | 200 | |
... | ... | |
322 | 312 |
if self.formdef.user_support == 'optional': |
323 | 313 |
self.has_user_support = True |
324 | 314 | |
325 |
def submitted(self, form, *args): |
|
326 |
super(CardFillPage, self).submitted(form, *args) |
|
315 |
def redirect_after_submitted(self, form, filled): |
|
316 |
if get_request().form.get('_popup'): |
|
317 |
popup_response_data = json.dumps( |
|
318 |
{ |
|
319 |
'value': str(filled.id), |
|
320 |
'obj': str(filled.digest), |
|
321 |
} |
|
322 |
) |
|
323 |
return template.QommonTemplateResponse( |
|
324 |
templates=['wcs/backoffice/popup_response.html'], |
|
325 |
context={'popup_response_data': popup_response_data}, |
|
326 |
is_django_native=True, |
|
327 |
) |
|
328 |
result = super().redirect_after_submitted(form, filled) |
|
327 | 329 |
if get_response().get_header('location').endswith('/backoffice/submission/'): |
328 | 330 |
return redirect('..') |
331 |
return result |
|
332 | ||
333 |
def create_form(self, *args, **kwargs): |
|
334 |
form = super().create_form(*args, **kwargs) |
|
335 |
if get_request().form.get('_popup'): |
|
336 |
form.add_hidden('_popup', 1) |
|
337 |
return form |
|
329 | 338 | |
330 | 339 | |
331 | 340 |
class CardBackOfficeStatusPage(FormBackOfficeStatusPage): |
wcs/backoffice/submission.py | ||
---|---|---|
348 | 348 |
self.set_tracking_code(filled) |
349 | 349 |
get_session().remove_magictoken(get_request().form.get('magictoken')) |
350 | 350 |
self.clean_submission_context() |
351 |
return self.redirect_after_submitted(form, filled) |
|
351 | 352 | |
353 |
def redirect_after_submitted(self, form, filled): |
|
352 | 354 |
url = filled.perform_workflow() |
353 | 355 |
if url: |
354 | 356 |
pass # always redirect to an URL the workflow returned |
wcs/carddef.py | ||
---|---|---|
150 | 150 |
base_url = get_publisher().get_frontoffice_url() |
151 | 151 |
return '%s/api/cards/%s/' % (base_url, self.url_name) |
152 | 152 | |
153 |
def can_user_add_cards(self, user): |
|
154 |
if not self.backoffice_submission_roles: |
|
155 |
return False |
|
156 |
for role in user.get_roles(): |
|
157 |
if role in self.backoffice_submission_roles: |
|
158 |
return True |
|
159 |
return False |
|
160 | ||
153 | 161 |
def store(self, comment=None): |
154 | 162 |
self.roles = self.backoffice_submission_roles |
155 | 163 |
return super().store(comment=comment) |
wcs/fields.py | ||
---|---|---|
1841 | 1841 | |
1842 | 1842 |
return self.display_mode |
1843 | 1843 | |
1844 |
def get_carddef(self): |
|
1845 |
from wcs.carddef import CardDef |
|
1846 | ||
1847 |
try: |
|
1848 |
return CardDef.get_by_urlname(self.data_source['type'][8:]) |
|
1849 |
except KeyError: |
|
1850 |
return None |
|
1851 | ||
1844 | 1852 |
def perform_more_widget_changes(self, form, kwargs, edit=True): |
1845 | 1853 |
data_source = data_sources.get_object(self.data_source) |
1846 | 1854 |
display_mode = self.get_display_mode(data_source) |
1847 | 1855 | |
1848 | 1856 |
if display_mode == 'autocomplete' and data_source and data_source.can_jsonp(): |
1849 | 1857 |
self.url = kwargs['url'] = data_source.get_jsonp_url() |
1858 |
carddef = self.get_carddef() |
|
1859 |
if carddef and carddef.can_user_add_cards(get_request().user): |
|
1860 |
kwargs['add_related_url'] = carddef.get_backoffice_submission_url() |
|
1850 | 1861 |
self.widget_class = JsonpSingleSelectWidget |
1851 | 1862 |
return |
1852 | 1863 | |
... | ... | |
1908 | 1919 |
and self.data_source.get('type', '').startswith('carddef:') |
1909 | 1920 |
): |
1910 | 1921 |
return value |
1911 |
from wcs.carddef import CardDef |
|
1912 | ||
1922 |
carddef = self.get_carddef() |
|
1923 |
if not carddef: |
|
1924 |
return value |
|
1913 | 1925 |
try: |
1914 |
carddef = CardDef.get_by_urlname(self.data_source['type'][8:]) |
|
1915 | 1926 |
carddata = carddef.data_class().get(value_id) |
1916 | 1927 |
except KeyError: |
1917 | 1928 |
return value |
wcs/qommon/form.py | ||
---|---|---|
2322 | 2322 |
class JsonpSingleSelectWidget(Widget): |
2323 | 2323 |
template_name = 'qommon/forms/widgets/select_jsonp.html' |
2324 | 2324 | |
2325 |
def __init__(self, name, value=None, url=None, **kwargs): |
|
2325 |
def __init__(self, name, value=None, url=None, add_related_url=None, **kwargs):
|
|
2326 | 2326 |
super(JsonpSingleSelectWidget, self).__init__(name, value=value, **kwargs) |
2327 | 2327 |
self.url = url |
2328 |
self.add_related_url = add_related_url |
|
2328 | 2329 | |
2329 | 2330 |
def add_media(self): |
2330 | 2331 |
get_response().add_javascript(['select2.js']) |
wcs/qommon/static/css/dc2/admin.scss | ||
---|---|---|
684 | 684 |
display: inline-block; |
685 | 685 |
} |
686 | 686 | |
687 |
div.JsonpSingleSelectWidget a.add-related { |
|
688 |
border-bottom: none; |
|
689 |
&::before { |
|
690 |
content: "\f067"; /* plus */ |
|
691 |
font-family: FontAwesome; |
|
692 |
padding-right: 1ex; |
|
693 |
} |
|
694 |
} |
|
695 | ||
687 | 696 |
aside#sidebar input.inline-input { |
688 | 697 |
margin-right: 1em; |
689 | 698 |
} |
wcs/qommon/static/js/popup_response.js | ||
---|---|---|
1 |
(function() { |
|
2 |
var initData = JSON.parse(document.getElementById('popup-response-constants').dataset.popupResponse); |
|
3 |
opener.dismissAddRelatedObjectPopup(window, initData.value, initData.obj); |
|
4 |
})(); |
wcs/qommon/static/js/qommon.admin.js | ||
---|---|---|
282 | 282 |
} |
283 | 283 |
}); |
284 | 284 |
$('[type=radio][name=display_mode]:checked').trigger('change'); |
285 | ||
286 |
// IE doesn't accept periods or dashes in the window name, but the element IDs |
|
287 |
// we use to generate popup window names may contain them, therefore we map them |
|
288 |
// to allowed characters in a reversible way so that we can locate the correct |
|
289 |
// element when the popup window is dismissed. |
|
290 |
function id_to_windowname(text) { |
|
291 |
text = text.replace(/\./g, '__dot__'); |
|
292 |
text = text.replace(/\-/g, '__dash__'); |
|
293 |
return text; |
|
294 |
} |
|
295 | ||
296 |
function windowname_to_id(text) { |
|
297 |
text = text.replace(/__dot__/g, '.'); |
|
298 |
text = text.replace(/__dash__/g, '-'); |
|
299 |
return text; |
|
300 |
} |
|
301 | ||
302 |
function showAddRelatedObjectPopup(triggeringLink) { |
|
303 |
var name = triggeringLink.id.replace(/^add_/, ''); |
|
304 |
name = id_to_windowname(name); |
|
305 |
console.log(name) |
|
306 |
var href = triggeringLink.href; |
|
307 |
console.log(href) |
|
308 |
var win = window.open(href, name, 'height=500,width=800,resizable=yes,scrollbars=yes'); |
|
309 |
win.focus(); |
|
310 |
return false; |
|
311 |
} |
|
312 | ||
313 |
function dismissAddRelatedObjectPopup(win, newId, newRepr) { |
|
314 |
var name = windowname_to_id(win.name); |
|
315 |
var elem = document.getElementById(name); |
|
316 |
if (elem) { |
|
317 |
var elemName = elem.nodeName.toUpperCase(); |
|
318 |
if (elemName === 'SELECT') { |
|
319 |
elem.options[elem.options.length] = new Option(newRepr, newId, true, true); |
|
320 |
} |
|
321 |
// Trigger a change event to update related links if required. |
|
322 |
$(elem).trigger('change'); |
|
323 |
} |
|
324 |
win.close(); |
|
325 |
} |
|
326 |
window.dismissAddRelatedObjectPopup = dismissAddRelatedObjectPopup; |
|
327 | ||
328 |
$('body').on('click', '.add-related', function(e) { |
|
329 |
e.preventDefault(); |
|
330 |
if (this.href) { |
|
331 |
showAddRelatedObjectPopup(this); |
|
332 |
} |
|
333 |
}); |
|
285 | 334 |
}); |
wcs/qommon/templates/qommon/forms/widgets/select_jsonp.html | ||
---|---|---|
1 | 1 |
{% extends "qommon/forms/widget.html" %} |
2 |
{% load i18n %} |
|
2 | 3 |
{% block widget-control %} |
3 | 4 |
<select id="form_{{widget.name}}" name="{{widget.name}}" |
4 | 5 |
data-select2-url="{{widget.get_select2_url}}" |
... | ... | |
6 | 7 |
data-required="{% if widget.is_required %}true{% endif %}" |
7 | 8 |
data-initial-display-value="{{widget.get_display_value|default_if_none:''}}"> |
8 | 9 |
</select> |
10 |
{% if widget.add_related_url %} |
|
11 |
<a class="add-related" id="add_form_{{ widget.name }}" |
|
12 |
href="{{ widget.add_related_url }}?_popup=1" |
|
13 |
title="{% blocktrans with card=widget.get_title %}Add another {{ card }}{% endblocktrans %}"> |
|
14 |
</a> |
|
15 |
{% endif %} |
|
9 | 16 |
{% endblock %} |
wcs/templates/wcs/backoffice/popup_response.html | ||
---|---|---|
1 |
{% load i18n static %}<!DOCTYPE html> |
|
2 |
<html> |
|
3 |
<head><title>{% trans 'Popup closing...' %}</title></head> |
|
4 |
<body> |
|
5 |
<script type="text/javascript" |
|
6 |
id="popup-response-constants" |
|
7 |
src="{% static "/js/popup_response.js" %}" |
|
8 |
data-popup-response="{{ popup_response_data }}"> |
|
9 |
</script> |
|
10 |
</body> |
|
11 |
</html> |
wcs/views.py | ||
---|---|---|
40 | 40 |
body.add_media() |
41 | 41 |
if body.is_django_native: |
42 | 42 |
self.template_names = body.templates[0] |
43 |
context.update(body.context) |
|
43 | 44 |
else: |
44 | 45 |
body = template.render(body.templates, body.context) |
45 | 46 |
self.template_names = None |
46 |
- |