Skip to content

Commit

Permalink
Merge remote-tracking branch 'webu/features/usable_attachments'
Browse files Browse the repository at this point in the history
# Conflicts:
#	pybb/views.py
  • Loading branch information
lampslave committed Sep 20, 2016
2 parents b2e7d00 + 263892e commit 93cb3cb
Show file tree
Hide file tree
Showing 14 changed files with 405 additions and 235 deletions.
Binary file modified pybb/locale/fr/LC_MESSAGES/django.mo
Binary file not shown.
402 changes: 189 additions & 213 deletions pybb/locale/fr/LC_MESSAGES/django.po

Large diffs are not rendered by default.

30 changes: 29 additions & 1 deletion pybb/markup/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,35 @@ def rstrip_str(user, str):
class BaseParser(object):
widget_class = Textarea

def format(self, text):
def format_attachments(self, text, attachments):
"""
Replaces attachment's references ([file-\d+]) inside a text with their related (web) URL
:param text: text which contains attachment's references
:type text: str or unicode
:param attachments: related attached files
:type attachments: Quersyset from a model with a "file" attribute.
:returns: str or unicode with [file-\d+] replaced by related file's (web) URL
"""

refs = re.findall( '\[file-([1-9][0-9]*)\]', text)
if not refs:
return text
refs = sorted(set(refs))

max_ref = attachments.count()
if not max_ref:
return text
refs = [int(ref) for ref in refs if int(ref) <= max_ref]
attachments = [a for a in attachments.order_by('pk')[0:max(refs)]]
for ref in refs:
text = text.replace('[file-%d]' % ref, attachments[ref-1].file.url)

return text

def format(self, text, instance=None):
if instance and instance.pk:
text = self.format_attachments(text, attachments=instance.attachments.all())
return escape(text)

def quote(self, text, username=''):
Expand Down
18 changes: 10 additions & 8 deletions pybb/markup/bbcode.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,13 +32,6 @@ def render(self, *args, **kwargs):
class BBCodeParser(BaseParser):
widget_class = BBCodeWidget

def _render_quote(self, name, value, options, parent, context):
if options and 'quote' in options:
origin_author_html = '<em>%s</em><br>' % options['quote']
else:
origin_author_html = ''
return '<blockquote>%s%s</blockquote>' % (origin_author_html, value)

def __init__(self):
self._parser = Parser()
self._parser.add_simple_formatter('img', '<img src="%(value)s">', replace_links=False)
Expand All @@ -47,7 +40,16 @@ def __init__(self):
swallow_trailing_newline=True)
self._parser.add_formatter('quote', self._render_quote, strip=True, swallow_trailing_newline=True)

def format(self, text):
def _render_quote(self, name, value, options, parent, context):
if options and 'quote' in options:
origin_author_html = '<em>%s</em><br>' % options['quote']
else:
origin_author_html = ''
return '<blockquote>%s%s</blockquote>' % (origin_author_html, value)

def format(self, text, instance=None):
if instance and instance.pk:
text = self.format_attachments(text, attachments=instance.attachments.all())
return smile_it(self._parser.format(text))

def quote(self, text, username=''):
Expand Down
4 changes: 3 additions & 1 deletion pybb/markup/markdown.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,9 @@ class MarkdownParser(BaseParser):
def __init__(self):
self._parser = Markdown(safe_mode='escape')

def format(self, text):
def format(self, text, instance=None):
if instance and instance.pk:
text = self.format_attachments(text, attachments=instance.attachments.all())
return smile_it(self._parser.convert(text))

def quote(self, text, username=''):
Expand Down
2 changes: 1 addition & 1 deletion pybb/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -282,7 +282,7 @@ class Meta(object):
body_text = models.TextField(_('Text version'))

def render(self):
self.body_html = _get_markup_formatter()(self.body)
self.body_html = _get_markup_formatter()(self.body, instance=self)
# Remove tags which was generated with the markup processor
text = strip_tags(self.body_html)
# Unescape entities which was generated with the markup processor
Expand Down
7 changes: 7 additions & 0 deletions pybb/signals.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,13 @@ def topic_saved(instance, **kwargs):
notify_forum_subscribers(instance)

def post_saved(instance, **kwargs):

if getattr(instance, '_post_saved_done', False):
#Do not spam users when post is saved more than once in a same request.
#For eg, when we parse attachments.
return

instance._post_saved_done = True
if not defaults.PYBB_DISABLE_NOTIFICATIONS:
notify_topic_subscribers(instance)

Expand Down
Binary file added pybb/static/pybb/img/image.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
90 changes: 90 additions & 0 deletions pybb/static/pybb/js/pybbjs.js
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,96 @@ jQuery(function ($) {
window.location.hash = '#id_body';
}
});

var attachments_form = $('#id_body').closest('FORM').find('.attachments-form') ;

if(attachments_form.length > 0){

var first_file_id = attachments_form.find('.attachment-list .attachment-item INPUT[type=hidden]').attr('value');
if(first_file_id){
//if we are editing a post with an already attached files, attachment list must be displayed
attachments_form.find('.attachment-list').show();
attachments_form.find('.attachment-link').hide();
}else{
attachments_form.find('.attachment-list').hide();
attachments_form.find('.attachment-link').show();
}

//Display "insert actions" only if we have a file for this attachment item.
attachments_form.find('.attachment-ref').each(function(i){
var file_id = $(this).closest('.attachment-item').find('INPUT[type=hidden]').attr('value');
if(file_id){
$(this).show();
}else{
$(this).hide();
}
});

//some content is only usefull if JS works. By default these contents are not displayed.
attachments_form.find('.attachment-ref-insert-link, .attachment-ref-insert-image').show().css('cursor', 'pointer');

attachments_form.find('.attachment-link').on('click', function(e){
/*Toggle attachment list and link to display it*/
e.preventDefault();
attachments_form.find('.attachment-link').toggle()
$(this).closest('.attachments-form').find('.attachment-list').toggle();
});

attachments_form.find('.attachment-ref-insert-link').on('click', function(e){
/*
Insert link code with the file reference.
Use window.pybb.insert_link to insert code, whatever markup language is used
*/
e.preventDefault();
var attachment_item = $(this).closest('.attachment-item'),
ref = attachment_item.find('.attachment-ref-value').text(),
file = attachment_item.find('INPUT[type=file]').get(0),
name = '';
if(file && file.files && file.files[0]){
name = file.files[0].name ;
}else if(file && file.value){
name = file.value.split('\\');
name = name[name.length - 1].split('/');
name = name[name.length - 1];
}
window.pybb.insert_link(ref, name);
});

attachments_form.find('.attachment-ref-insert-image').on('click', function(e){
/*
Insert image code with the file reference.
Use window.pybb.insert_image to insert code, whatever markup language is used
*/
e.preventDefault();
var ref = $(this.parentNode).find('.attachment-ref-value').text();
window.pybb.insert_image(ref);
});

function add_pybb_attachment_item() {
/*
Add another attachment formset when we just add a new file.
*/
if(this.value){
$(this).unbind('change', add_pybb_attachment_item);
$(this).closest('.attachment-item').find('.attachment-ref').toggle();
var offset = parseInt($('#id_attachments-TOTAL_FORMS').attr('value')),
input_list, new_item;
if (offset < parseInt($('#id_attachments-MAX_NUM_FORMS').attr('value'))) {
input_list = attachments_form.find('.attachment-list');
new_item = input_list.find('.attachment-item').last().clone(true);
new_item.find('#id_attachments-'+ (offset-1) +'-id').attr('id', 'id_attachments-' + offset + '-id' ).attr('name','id_attachments-' + offset + '-id' );
new_item.find('#id_attachments-' + (offset-1) + '-file').replaceWith('<input id="id_attachments-' + offset + '-file" type="file" name="attachments-' + offset + '-file" />');
new_item.find('.attachment-ref-value').html('[file-' + (offset + 1) + ']');
input_list.append(new_item);
new_item.find('.attachment-ref').toggle();
$('#id_attachments-TOTAL_FORMS').attr('value', offset + 1);
current_input_file = $('#id_attachments-' + offset + '-file');
current_input_file.on('change', add_pybb_attachment_item);
};
}
}
attachments_form.find('INPUT[type=file]').last().on('change', add_pybb_attachment_item);
}
}

var $pybbConcernedTopics = $('#pybb_concerned_topics');
Expand Down
35 changes: 24 additions & 11 deletions pybb/templates/pybb/attachments_formset.html
Original file line number Diff line number Diff line change
@@ -1,17 +1,30 @@
{% load pybb_tags %}
{% load pybb_tags static %}

{% if request.user|pybb_may_attach_files %}
{% load i18n %}
<div class='attachments-form'>
<a class='attachment-link' onclick='$("div.attachments-form table").toggle();return false;' href='#'>{% trans "Add attachments" %}</a>
<div class="attachments-form">
<a class='attachment-link' style="display:none;" href='#'>{% trans "Add attachments" %}</a>
{{ aformset.management_form }}
<table style='display:none;'>
{% for form in aformset %}
<tr>
<td>{{ form.id }} {{ form.file }}</td>
<td>{% if form.instance.pk %}{{ form.DELETE }} {% trans "delete" %}{% endif %}</td>
</tr>
{% endfor %}
<table class="attachment-list">
{% for form in aformset %}
<tr class="attachment-item">
<td>
{{ form.id }}{{ form.file }}
{% if attachment_max_size %}
<span title="{% trans 'If you need to upload larger files, please use an external storage website.' %}" class="help">{% blocktrans with max_size=attachment_max_size|filesizeformat%}(max size : {{ max_size }}){% endblocktrans %}</span>
{% endif %}
</td>
<td>{% if form.instance.pk %}{{ form.DELETE }} {% trans "delete" %}{% endif %}</td>
<td>
<span class="attachment-ref">
<abbr title="{% trans 'Reference to your file' %}">Ref</abbr> :
<span class="attachment-ref-value">[file-{{ forloop.counter }}]</span>
<img style="display:none;" class="attachment-ref-insert-link" src="{% static 'pybb/img/attachment.png' %}" alt="{% trans 'link' %}" title="{% trans 'Insert a link to your file inside your post.' %}" />
<img style="display:none;" class="attachment-ref-insert-image" src="{% static 'pybb/img/image.png' %}" alt="{% trans 'image' %}" title="{% trans 'Insert your image inside your post.' %}" />
</span>
</td>
</tr>
{% endfor %}
</table>
</div>
{% endif %}
{% endif %}
6 changes: 6 additions & 0 deletions pybb/templates/pybb/markup/bbcode_widget.html
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,12 @@
];
return mySettings ;
};
window.pybb.insert_image = function(url){
$.markItUp({replaceWith: '[img]'+url+'[/img]'});
}
window.pybb.insert_link = function(url, name){
$.markItUp({replaceWith: '[url='+url+']'+name+'[/url]'});
}
}

if (!window.pybb.markup) {
Expand Down
6 changes: 6 additions & 0 deletions pybb/templates/pybb/markup/markdown_widget.html
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,12 @@
];
return mySettings ;
};
window.pybb.insert_image = function(url){
$.markItUp({replaceWith: '![]('+url+')'});
}
window.pybb.insert_link = function(url, name){
$.markItUp({replaceWith: '['+name+']('+url+')'});
}
}

if (!window.pybb.markup) {
Expand Down
36 changes: 36 additions & 0 deletions pybb/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -1517,6 +1517,8 @@ def test_attachment_one(self):
response = self.client.post(add_post_url, values, follow=True)
self.assertEqual(response.status_code, 200)
self.assertTrue(Post.objects.filter(body='test attachment').exists())
post = Post.objects.filter(body='test attachment')[0]
self.assertEqual(post.attachments.count(), 1)

def test_attachment_two(self):
add_post_url = reverse('pybb:add_post', kwargs={'topic_id': self.topic.id})
Expand All @@ -1531,6 +1533,40 @@ def test_attachment_two(self):
with self.assertRaises(ValidationError):
self.client.post(add_post_url, values, follow=True)

def test_attachment_usage(self):
add_post_url = reverse('pybb:add_post', kwargs={'topic_id': self.topic.id})
self.login_client()
response = self.client.get(add_post_url)
body = (
'test attachment: '
'[img][file-1][/img]'
'[img][file-2][/img]'
'[img][file-1][/img]'
'[file-3]'
'[file-a]'
)
with open(self.file_name, 'rb') as fp, open(self.file_name, 'rb') as fp2:
values = self.get_form_values(response)
values['body'] = body
values['attachments-0-file'] = fp
values['attachments-1-file'] = fp2
values['attachments-TOTAL_FORMS'] = 2
response = self.client.post(add_post_url, values, follow=True)
self.assertEqual(response.status_code, 200)
post = response.context['post']
imgs = html.fromstring(post.body_html).xpath('//img')
self.assertEqual(len(imgs), 3)
self.assertTrue('[file-3]' in post.body_html)
self.assertTrue('[file-a]' in post.body_html)

src1 = imgs[0].attrib.get('src')
src2 = imgs[1].attrib.get('src')
src3 = imgs[2].attrib.get('src')
attachments = [a for a in post.attachments.order_by('pk')]
self.assertEqual(src1, attachments[0].file.url)
self.assertEqual(src2, attachments[1].file.url)
self.assertEqual(src1, src3)

def tearDown(self):
defaults.PYBB_ATTACHMENT_ENABLE = self.PYBB_ATTACHMENT_ENABLE
defaults.PYBB_PREMODERATION = self.ORIG_PYBB_PREMODERATION
Expand Down
4 changes: 4 additions & 0 deletions pybb/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -334,6 +334,7 @@ def get_context_data(self, **kwargs):
if perms.may_attach_files(self.request.user):
aformset = self.get_attachment_formset_class()()
ctx['aformset'] = aformset
ctx['attachment_max_size'] = defaults.PYBB_ATTACHMENT_SIZE_LIMIT
if defaults.PYBB_FREEZE_FIRST_POST:
ctx['first_post'] = self.topic.head
else:
Expand Down Expand Up @@ -466,6 +467,9 @@ def form_valid(self, form):
self.object.save()
if save_attachments:
aformset.save()
if self.object.attachments.count():
# re-parse the body to replace attachment's references by URLs
self.object.save()
if save_poll_answers:
pollformset.save()
return HttpResponseRedirect(self.get_success_url())
Expand Down

0 comments on commit 93cb3cb

Please sign in to comment.