[ADD] tentative removal of attachments

Attachments ought be removed only if they are not used in an existing page/web
view.

Theoretically this could be set directly in unlink(), but:
* that would make a nice error message significantly harder
* the expenses of performing a text search in all view archs would be a bit expensive

Notes:
* the views set could be reduced to only "web" views
* the search is likely sensible to false negatives e.g. different order of
  query parameters. It *will* remove images still being used.

bzr revid: xmo@openerp.com-20140304110716-u14w6uo8fbkfa42i
This commit is contained in:
Xavier Morel
2014-03-04 12:07:16 +01:00
parent f363856773
commit e78a3b18cc
5 changed files with 116 additions and 4 deletions
+28
View File
@@ -9,6 +9,7 @@ import urlparse
import werkzeug
import werkzeug.exceptions
import werkzeug.utils
import werkzeug.wrappers
# optional python-slugify import (https://github.com/un33k/python-slugify)
try:
@@ -594,6 +595,33 @@ class ir_attachment(osv.osv):
return super(ir_attachment, self).create(
cr, uid, values, context=context)
def try_remove(self, cr, uid, ids, context=None):
""" Removes a web-based image attachment if it is used by no view
(template)
Returns a dict mapping attachments which would not be removed (if any)
mapped to the views preventing their removal
"""
Views = self.pool['ir.ui.view']
attachments_to_remove = []
# views blocking removal of the attachment
removal_blocked_by = {}
for attachment in self.browse(cr, uid, ids, context=context):
# in-document URLs are html-escaped, a straight search will not
# find them
url = werkzeug.utils.escape(attachment.website_url)
ids = Views.search(cr, uid, [('arch', 'like', url)], context=context)
if ids:
removal_blocked_by[attachment.id] = Views.read(
cr, uid, ids, ['name'], context=context)
else:
attachments_to_remove.append(attachment.id)
if attachments_to_remove:
self.unlink(cr, uid, attachments_to_remove, context=context)
return removal_blocked_by
class res_partner(osv.osv):
_inherit = "res.partner"
+20
View File
@@ -251,6 +251,26 @@ ul.oe_menu_editor .disclose {
.existing-attachments .pager .disabled {
display: none;
}
.existing-attachments .existing-attachment-cell {
position: relative;
}
.existing-attachments .existing-attachment-cell .img {
border: 1px solid #848490;
}
.existing-attachments .existing-attachment-cell .existing-attachment-remove {
position: absolute;
top: 0;
left: 15px;
cursor: pointer;
background: white;
padding: 2px;
border: 1px solid #848490;
border-top: none;
border-left: none;
-moz-border-radius-bottomright: 8px;
-webkit-border-bottom-right-radius: 8px;
border-bottom-right-radius: 8px;
}
.cke_widget_wrapper {
position: static !important;
+20 -2
View File
@@ -211,9 +211,27 @@ ul.oe_menu_editor
.font-icons-selected
background-color: #ddd
$attachment-border-color: #848490
.existing-attachments
.pager .disabled
display: none
.existing-attachments .pager .disabled
display: none
.existing-attachment-cell
position: relative
.img
border: 1px solid $attachment-border-color
.existing-attachment-remove
position: absolute
top: 0
left: 15px // padding-left on col-*
cursor: pointer
background: white
padding: 2px
border: 1px solid $attachment-border-color
border-top: none
border-left: none
+border-bottom-right-radius(8px)
// wrapper positioned relatively for drag&drop widget which is disabled below.
// Breaks completely horribly crazy products listing page, so take it out.
@@ -1441,6 +1441,7 @@
this.page += $target.hasClass('previous') ? -1 : 1;
this.display_attachments();
},
'click .existing-attachment-remove': 'try_remove',
}),
init: function (parent) {
this.image = null;
@@ -1473,6 +1474,7 @@
this.display_attachments();
},
display_attachments: function () {
this.$('.help-block').empty();
var per_screen = IMAGES_PER_ROW * IMAGES_ROWS;
var from = this.page * per_screen;
@@ -1500,6 +1502,34 @@
}
this.close()
},
try_remove: function (e) {
var $help_block = this.$('.help-block').empty();
var self = this;
var id = parseInt($(e.target).data('id'), 10);
var attachment = _.findWhere(this.records, {id: id});
return openerp.jsonRpc('/web/dataset/call_kw', 'call', {
model: 'ir.attachment',
method: 'try_remove',
args: [],
kwargs: {
ids: [id],
context: website.get_context()
}
}).then(function (prevented) {
if (_.isEmpty(prevented)) {
self.records = _.without(self.records, attachment);
self.display_attachments();
return;
}
$help_block.replaceWith(openerp.qweb.render(
'website.editor.dialog.image.existing.error', {
views: prevented[id]
}
));
});
},
});
function get_selected_link(editor) {
@@ -140,8 +140,9 @@
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
<h3 class="modal-title">Select a Picture</h3>
</div>
<div class="modal-body">
<div class="modal-body has-error">
<div class="existing-attachments"/>
<div class="help-block"/>
</div>
<div class="modal-footer">
<a href="#" data-dismiss="modal" aria-hidden="true">Discard</a>
@@ -157,12 +158,27 @@
<li class="next disabled"><a href="#">Next →</a></li>
</ul>
<div class="row mt16" t-foreach="rows" t-as="row">
<div class="col-sm-2" t-foreach="row" t-as="attachment">
<div class="col-sm-2 existing-attachment-cell"
t-foreach="row" t-as="attachment">
<i class="fa fa-times existing-attachment-remove" t-att-data-id="attachment.id"/>
<img t-att-src="attachment.website_url" t-att-alt="attachment.name" class="img img-responsive"/>
</div>
</div>
</div>
</t>
<t t-name="website.editor.dialog.image.existing.error">
<div class="help-block">
<p>The image could not be deleted because it is used in the
following pages or views:</p>
<ul t-foreach="views" t-as="view">
<li>
<a t-attf-href="/web#model=ir.ui.view&amp;id=#{view.id}">
<t t-esc="view.name"/>
</a> (id <t t-esc="view.id"/>)
</li>
</ul>
</div>
</t>
<t t-name="website.editor.table.panel">
<table class="editorbar-panel">
<tr t-foreach="rows"><td t-foreach="cols">&#8203;</td></tr>