cleaning, coping with adding and removing attachments in same request, auto show attachments, fix syntax highlighting (bug 612293 bug 618500)

This commit is contained in:
Andy McKay 2010-11-24 14:49:56 -08:00
Родитель 013e48c328
Коммит f2cc3835bb
22 изменённых файлов: 772 добавлений и 464 удалений

Просмотреть файл

@ -1,6 +1,6 @@
from django.db.models.manager import Manager
from django.shortcuts import _get_queryset
from django.http import Http404
from django.http import Http404, HttpRequest
def get_object_or_create(klass, *args, **kwargs):

Просмотреть файл

@ -3,7 +3,7 @@
var FDBespin = new Class({
Implements: [Options, Events],
options: {
validSyntaxes: ['js', 'html', 'plain']
validSyntaxes: ['js', 'html', 'plain', 'css']
},
initialize: function(element, options) {
var self = this;
@ -43,15 +43,10 @@ var FDBespin = new Class({
},
setSyntax: function(syntax) {
if (!this.options.validSyntaxes.contains(syntax)) {
if (syntax == 'css') {
syntax = 'html'
if (syntax == 'json') {
syntax = 'js'
} else {
if (syntax == 'json') {
syntax = 'js'
} else {
syntax = 'plain'
}
syntax = 'plain'
}
}
// XXX Switched off as incompatible with MooTools

Просмотреть файл

@ -1,5 +1,5 @@
/*
* Extending Flightdeck with Editor functionality
* Extending Flightdeck with Editor functionality
*/
FlightDeck = Class.refactor(FlightDeck,{
@ -59,7 +59,7 @@ FlightDeck = Class.refactor(FlightDeck,{
} else {
assignToEl(selector);
}
$('modules').addEvent('click:relay(.{file_listing_class} li a)'.substitute(this.options),
$('modules').addEvent('click:relay(.{file_listing_class} li a)'.substitute(this.options),
function(e, el) {
var li = $(el).getParent('li');
// assign switch_mode_on to newly created modules
@ -87,5 +87,5 @@ FlightDeck = Class.refactor(FlightDeck,{
}
}, this);
}
});

Просмотреть файл

@ -31,14 +31,17 @@ var Package = new Class({
// origin_url: '', // link to a revision used to created this one
// revision_author: '',
// modules: [], // a list of module filename, author pairs
attachments: [],
readonly: false,
package_info_el: 'package-info',
test_el: 'try_in_browser'
},
modules: {},
attachments: {},
initialize: function(options) {
this.setOptions(options);
this.instantiate_modules();
this.instantiate_attachments();
$('revisions_list').addEvent('click', this.show_revision_list);
// testing
@ -47,30 +50,6 @@ var Package = new Class({
this.test_url = $(this.options.test_el).get('href');
$(this.options.test_el).addEvent('click', this.boundTestAddon)
}
if ($('attachments')) $('attachments').addEvent(
'click:relay(.UI_File_Listing a)',
function(e, target) {
e.stop();
var url = target.get('href');
var ext = target.get('rel');
var filename = target.get('text').escapeAll();
var template_start = '<div id="attachment_view"><h3>'+filename+'</h3><div class="UI_Modal_Section">';
var template_end = '</div><div class="UI_Modal_Actions"><ul><li><input type="reset" value="Close" class="closeModal"/></li></ul></div></div>';
var template_middle = 'Download <a href="'+url+'">'+filename+'</a>';
if (['jpg', 'gif', 'png'].contains(ext)) template_middle = '<img src="'+url+'"/>';
if (['css', 'js', 'txt'].contains(ext)) {
new Request({
url: url,
onSuccess: function(response) {
template_middle = '<pre>'+response.escapeAll()+'</pre>';
this.attachmentWindow = fd.displayModal(template_start+template_middle+template_end);
}
}).send();
} else {
this.attachmentWindow = fd.displayModal(template_start+template_middle+template_end);
}
}.bind(this)
)
},
testAddon: function(e){
var el;
@ -89,12 +68,12 @@ var Package = new Class({
} else {
fd.whenAddonInstalled(function() {
fd.message.alert(
'Add-on Builder Helper',
'Add-on Builder Helper',
'Now that you have installed the Add-ons Builder Helper, loading the add-on into your browser for testing...'
);
this.testAddon();
}.bind(this));
}
},
installAddon: function() {
@ -121,6 +100,13 @@ var Package = new Class({
this.modules[module.filename] = new Module(this,module);
}, this);
},
instantiate_attachments: function() {
// iterate through attachments
this.options.attachments.each(function(attachment) {
attachment.readonly = this.options.readonly;
this.attachments[attachment.uid] = new Attachment(this,attachment);
}, this);
},
show_revision_list: function(e) {
if (e) e.stop();
new Request({
@ -132,8 +118,164 @@ var Package = new Class({
}
});
var File = Class({
destroy: function() {
// refactor me
if (this.textarea) this.textarea.destroy();
this.trigger.getParent('li').destroy();
$('attachments-counter').set('text', '('+ $(this.options.counter).getElements('.UI_File_Listing li').length +')')
delete fd.getItem().modules[this.options.filename];
delete fd.editor_contents[this.get_editor_id()];
if (this.active) {
// switch editor!
mod = null;
// try to switch to first element
first = false;
$each(fd.getItem().modules, function(mod) {
if (!first) {
first = true;
mod.switchBespin();
mod.trigger.getParent('li').switch_mode_on();
}
});
if (!first) {
fd.cleanBespin();
}
}
},
switchBespin: function() {
if (!fd.editor_contents[this.get_editor_id()]) {
this.loadCode();
}
fd.switchBespinEditor(this.get_editor_id(), this.options.type);
if (fd.getItem()) {
$each(fd.getItem().modules, function(mod) {
mod.active = false;
});
}
this.active = true;
},
get_editor_id: function() {
if (!this._editor_id)
this._editor_id = this.get_css_id() + this.options.code_editor_suffix;
return this._editor_id;
},
get_trigger_id: function() {
if (!this._trigger_id)
this._trigger_id = this.get_css_id() + this.options.code_trigger_suffix;
return this._trigger_id;
},
});
var Attachment = new Class({
Extends: File,
Implements: [Options, Events],
options: {
code_trigger_suffix: '_attachment_switch', // id of an element which is used to switch editors
code_editor_suffix: '_attachment_textarea', // id of the textarea
active: false,
type: 'js',
append: false,
readonly: false,
counter: 'attachments'
},
is_editable: function() {
return ['css', 'txt', 'js', 'html'].contains(this.options.type);
},
initialize: function(pack, options) {
this.setOptions(options);
this.pack = pack;
if (this.options.append) {
this.append();
}
// connect trigger with editor
if ($(this.get_trigger_id())) {
this.trigger = $(this.get_trigger_id());
this.trigger.store('Attachment', this);
this.editor = new FDEditor({
element: this.get_editor_id(),
activate: this.options.main || this.options.executable,
type: this.options.type,
readonly: this.options.readonly
});
// connect trigger
this.trigger.addEvent('click', function(e) {
if (e) e.preventDefault();
if (this.is_editable()) {
this.switchBespin();
this.highlightMenu();
} else {
var target = e.target;
var url = target.get('href');
var ext = target.get('rel');
var filename = target.get('text').escapeAll();
var template_start = '<div id="attachment_view"><h3>'+filename+'</h3><div class="UI_Modal_Section">';
var template_end = '</div><div class="UI_Modal_Actions"><ul><li><input type="reset" value="Close" class="closeModal"/></li></ul></div></div>';
var template_middle = 'Download <a href="'+url+'">'+filename+'</a>';
if (['jpg', 'gif', 'png'].contains(ext)) template_middle += '<p><img src="'+url+'"/></p>';
this.attachmentWindow = fd.displayModal(template_start+template_middle+template_end);
}
}.bind(this));
if (this.options.active && this.is_editable()) {
this.switchBespin();
this.highlightMenu();
}
if (!this.options.readonly) {
// here special functionality for edit page
var rm_mod_trigger = this.trigger.getElement('span.File_close');
if (rm_mod_trigger) {
rm_mod_trigger.addEvent('click', function(e) {
this.pack.removeAttachmentAction(e);
}.bind(this));
}
}
}
},
highlightMenu: function() {
var li = this.trigger.getParent('li')
fd.assignModeSwitch(li);
li.switch_mode_on();
},
loadCode: function() {
// load data synchronously
new Request({
url: this.options.get_url,
async: false,
useSpinner: true,
spinnerTarget: 'editor-wrapper',
onSuccess: function(text, html) {
fd.editor_contents[this.get_editor_id()] = text;
}.bind(this)
}).send();
},
get_css_id: function() {
return this.options.uid;
},
append: function() {
var html = '<a title="" href="'+ this.options.get_url + '" class="Module_file" id="' + this.get_trigger_id() + '">'+
'{filename}.{ext}<span class="File_status"></span>'+
'<span class="File_close"></span>'+
'</a>';
var li = new Element('li',{
'class': 'UI_File_normal',
'html': html.substitute(this.options)
}).inject($('add_attachment_div').getPrevious('ul'));
$('attachments-counter').set('text', '('+ $('attachments').getElements('.UI_File_Listing li').length +')')
if (this.is_editable()) {
var textarea = new Element('textarea', {
'id': this.get_editor_id(),
'class': 'UI_Editor_Area',
'name': this.get_editor_id(),
'html': ''
}).inject('editor-wrapper');
}
}
});
var Module = new Class({
Extends: File,
Implements: [Options, Events],
options: {
// data
@ -148,7 +290,8 @@ var Module = new Class({
executable: false,
active: false,
type: 'js',
append: false
append: false,
counter: 'modules'
},
initialize: function(pack, options) {
this.setOptions(options);
@ -173,80 +316,35 @@ var Module = new Class({
}.bind(this));
if (this.options.main || this.options.executable) {
this.trigger.getParent('li').switch_mode_on();
}
}
if (this.options.active) {
this.switchBespin();
var li = this.trigger.getParent('li')
fd.assignModeSwitch(li);
li.switch_mode_on();
}
if (!this.options.readonly) {
// here special functionality for edit page
var rm_mod_trigger = this.trigger.getElement('span.File_close');
if (rm_mod_trigger) {
rm_mod_trigger.addEvent('click', function(e) {
this.pack.removeModuleAction(e);
}.bind(this));
}
if (!this.options.readonly) {
// here special functionality for edit page
var rm_mod_trigger = this.trigger.getElement('span.File_close');
if (rm_mod_trigger) {
rm_mod_trigger.addEvent('click', function(e) {
this.pack.removeModuleAction(e);
}.bind(this));
}
}
}
},
loadCode: function() {
// load data synchronously
new Request.JSON({
url: this.options.get_url,
async: false,
useSpinner: true,
spinnerTarget: 'editor-wrapper',
onSuccess: function(mod) {
fd.editor_contents[this.get_editor_id()] = mod.code;
}.bind(this)
}).send();
},
switchBespin: function() {
if (!fd.editor_contents[this.get_editor_id()]) {
this.loadCode();
}
fd.switchBespinEditor(this.get_editor_id(), this.options.type);
if (fd.getItem()) {
$each(fd.getItem().modules, function(mod) {
mod.active = false;
});
}
this.active = true;
},
destroy: function() {
if (this.textarea) this.textarea.destroy();
this.trigger.getParent('li').destroy();
$('modules-counter').set('text', '('+ $('modules').getElements('.UI_File_Listing li').length +')')
delete fd.getItem().modules[this.options.filename];
delete fd.editor_contents[this.get_editor_id()];
if (this.active) {
// switch editor!
mod = null;
// try to switch to first element
first = false;
$each(fd.getItem().modules, function(mod) {
if (!first) {
first = true;
mod.switchBespin();
mod.trigger.getParent('li').switch_mode_on();
}
});
if (!first) {
fd.cleanBespin();
}
}
},
get_editor_id: function() {
if (!this._editor_id)
this._editor_id = this.options.filename + this.options.code_editor_suffix;
return this._editor_id;
},
get_trigger_id: function() {
if (!this._trigger_id)
this._trigger_id = this.options.filename + this.options.code_trigger_suffix;
return this._trigger_id;
loadCode: function() {
// load data synchronously
new Request.JSON({
url: this.options.get_url,
async: false,
useSpinner: true,
spinnerTarget: 'editor-wrapper',
onSuccess: function(mod) {
fd.editor_contents[this.get_editor_id()] = mod.code;
}.bind(this)
}).send();
},
append: function() {
var html = '<a title="" href="#" class="Module_file" id="{filename}_switch">'+
@ -258,16 +356,19 @@ var Module = new Class({
'html': html.substitute(this.options)
}).inject($('add_module_div').getPrevious('ul'));
$('modules-counter').set('text', '('+ $('modules').getElements('.UI_File_Listing li').length +')')
var textarea = new Element('textarea', {
'id': this.options.filename + '_textarea',
'class': 'UI_Editor_Area',
'name': this.options.filename + '_textarea',
'html': this.options.code
}).inject('editor-wrapper');
}
})
},
get_css_id: function() {
return this.options.filename;
},
})
Package.View = new Class({
Extends: Package,
@ -353,51 +454,48 @@ Package.Edit = new Class({
// assign menu items
this.appSidebarValidator = new Form.Validator.Inline('app-sidebar-form');
$(this.options.package_info_el).addEvent('click', this.editInfo.bind(this));
// save
this.boundSaveAction = this.saveAction.bind(this);
$(this.options.save_el).addEvent('click', this.boundSaveAction);
// submit Info
this.boundSubmitInfo = this.submitInfo.bind(this);
// add/remove module
this.boundAddModuleAction = this.addModuleAction.bind(this);
this.boundRemoveModuleAction = this.removeModuleAction.bind(this);
$(this.options.add_module_el).addEvent('click',
$(this.options.add_module_el).addEvent('click',
this.boundAddModuleAction);
// assign/remove library
this.boundAssignLibraryAction = this.assignLibraryAction.bind(this);
this.boundRemoveLibraryAction = this.removeLibraryAction.bind(this);
$(this.options.assign_library_el).addEvent('click',
this.boundAssignLibraryAction);
$$('#libraries .UI_File_Listing .File_close').each(function(close) {
$$('#libraries .UI_File_Listing .File_close').each(function(close) {
close.addEvent('click', this.boundRemoveLibraryAction);
},this);
// add attachments
this.add_attachment_el = $('add_attachment');
this.attachment_template = '<a title="" rel="{ext}" href="{display_url}" class="Module_file" id="{filename}{ext}_display">'+
'{basename}<span class="File_close"></span>'+
'</a>';
this.add_attachment_el.addEvent('change', this.sendMultipleFiles.bind(this));
this.boundRemoveAttachmentAction = this.removeAttachmentAction.bind(this);
$$('#attachments .UI_File_Listing .File_close').each(function(close) {
$$('#attachments .UI_File_Listing .File_close').each(function(close) {
close.addEvent('click', this.boundRemoveAttachmentAction);
},this);
this.attachments_counter = $('attachments-counter');
var fakeFileInput = $('add_attachment_fake'), fakeFileSubmit = $('add_attachment_action_fake');
this.add_attachment_el.addEvents({
change: function(){
fakeFileInput.set('value', this.get('value'));
},
mouseover: function(){
fakeFileSubmit.addClass('hover');
},
mouseout: function(){
fakeFileSubmit.removeClass('hover');
}
@ -415,7 +513,7 @@ Package.Edit = new Class({
// change url to the SDK lib code
$('core_library_lib').getElement('a').set(
'href', response.lib_url);
// change name of the SDK lib
// change name of the SDK lib
$('core_library_lib').getElement('span').set(
'text', response.lib_name);
fd.message.alert(response.message_title, response.message);
@ -431,56 +529,60 @@ Package.Edit = new Class({
sendMultipleFiles: function() {
self = this;
self.spinner = false;
self.spinner = false;
sendMultipleFiles({
url: this.get_add_attachment_url.bind(this),
// list of files to upload
files: this.add_attachment_el.files,
// clear the container
onloadstart:function(){
if (self.spinner) {
self.spinner.position();
} else {
self.spinner = new Spinner($('attachments')).show();
}
if (self.spinner) {
self.spinner.position();
} else {
self.spinner = new Spinner($('attachments')).show();
}
},
// do something during upload ...
//onprogress:function(rpe){
// $log('progress');
//},
onpartialload: function(rpe, xhr) {
$log('FD: file uploaded');
// here parse xhr.responseText and append a DOM Element
$log('FD: attachment uploaded');
response = JSON.parse(xhr.responseText);
new Element('li',{
'class': 'UI_File_Normal',
'html': self.attachment_template.substitute(response)
}).inject($('attachments_ul'));
$(response.filename+response.ext+'_display').getElement('.File_close').addEvent('click', self.boundRemoveAttachmentAction);
fd.setURIRedirect(response.view_url);
self.setUrls(response);
self.attachments_counter.set('text', '('+ $('attachments').getElements('.UI_File_Listing li').length +')')
fd.message.alert(response.message_title, response.message);
var attachment = new Attachment(self,{
append: true,
active: true,
filename: response.filename,
ext: response.ext,
author: response.author,
code: response.code,
get_url: response.get_url,
uid: response.uid,
type: response.ext
});
self.attachments[response.uid] = attachment;
},
// fired when last file has been uploaded
onload:function(rpe, xhr){
if (self.spinner) self.spinner.destroy();
if (self.spinner) self.spinner.destroy();
$log('FD: all files uploaded');
$(self.add_attachment_el).set('value','');
$('add_attachment_fake').set('value','')
},
// if something is wrong ... (from native instance or because of size)
onerror:function(){
if (self.spinner) self.spinner.destroy();
if (self.spinner) self.spinner.destroy();
fd.error.alert(
'Error {status}'.substitute(xhr),
'Error {status}'.substitute(xhr),
'{statusText}<br/>{responseText}'.substitute(xhr)
);
);
}
});
},
@ -507,8 +609,8 @@ Package.Edit = new Class({
onSuccess: function(response) {
fd.setURIRedirect(response.view_url);
self.setUrls(response);
$(response.filename+response.ext+'_display').getParent('li').destroy();
self.attachments_counter.set('text', '('+ $('attachments').getElements('.UI_File_Listing li').length +')')
var attachment = self.attachments[response.uid];
attachment.destroy();
}
}).send();
},
@ -545,7 +647,7 @@ Package.Edit = new Class({
filename: response.filename,
author: response.author,
code: response.code,
get_url: response.get_url
get_url: response.get_url
});
this.modules[response.filename] = mod;
}.bind(this)
@ -619,7 +721,7 @@ Package.Edit = new Class({
'class': 'UI_File_Normal',
'html': html.substitute(lib)
}).inject($('assign_library_div').getPrevious('ul'));
$$('#library_{library_name} .File_close'.substitute(lib)).each(function(close) {
$$('#library_{library_name} .File_close'.substitute(lib)).each(function(close) {
close.addEvent('click', this.boundRemoveLibraryAction);
},this);
$('libraries-counter').set('text', '('+ $('libraries').getElements('.UI_File_Listing li').length +')')
@ -661,7 +763,7 @@ Package.Edit = new Class({
this.savenow = false;
fd.editPackageInfoModal = fd.displayModal(settings.edit_package_info_template.substitute(this.data || this.options));
$('package-info_form').addEvent('submit', this.boundSubmitInfo);
// XXX: this will change after moving the content to other forms
$('version_name').addEvent('change', function() { fd.fireEvent('change'); });
$('full_name').addEvent('change', function() { fd.fireEvent('change'); });
@ -709,6 +811,9 @@ Package.Edit = new Class({
$each(this.modules, function(module, filename) {
this.data[filename] = fd.editor_contents[filename + module.options.code_editor_suffix]
}, this);
$each(this.attachments, function(attachment) {
this.data[attachment.options.uid] = fd.editor_contents[attachment.options.uid + attachment.options.code_editor_suffix]
}, this);
},
testAddon: function(e){
this.collectData();
@ -745,7 +850,7 @@ Package.Edit = new Class({
if ($(this.options.test_el).getParent('li').hasClass('pressed')) {
// only one add-on of the same id should be allowed on the Helper side
this.installAddon();
}
}
fd.fireEvent('save');
}.bind(this),
onFailure: function() {

Просмотреть файл

@ -5,6 +5,7 @@
import os
import csv
import shutil
import time
from copy import deepcopy
@ -201,7 +202,6 @@ class Package(models.Model):
i = i + 1
return _get_full_name(full_name, username, type_id, i)
name = settings.DEFAULT_PACKAGE_FULLNAME.get(self.type,
self.author.username)
self.full_name = _get_full_name(name, self.author.username, self.type)
@ -557,7 +557,7 @@ class PackageRevision(models.Model):
returns False if the package revision contains a module with given
filename
"""
if self.modules.filter(filename=filename).count() > 0:
if self.modules.filter(filename=filename).count():
return False
return True
@ -566,7 +566,7 @@ class PackageRevision(models.Model):
returns False if the package revision contains a module with given
filename
"""
if self.attachments.filter(filename=filename, ext=ext).count() > 0:
if self.attachments.filter(filename=filename, ext=ext).count():
return False
return True
@ -611,40 +611,44 @@ class PackageRevision(models.Model):
self.save()
return self.modules.remove(mod)
def module_update(self, mod, save=True):
def update(self, change, save=True):
" to update a module, new package revision has to be created "
if save:
self.save()
self.modules.remove(mod)
mod.id = None
mod.save()
self.modules.add(mod)
def modules_update(self, modules):
" update more than one module "
change.increment(self)
def updates(self, changes):
"""Changes from the server."""
self.save()
for mod in modules:
self.module_update(mod, False)
for change in changes:
self.update(change, False)
def attachment_create(self, **kwargs):
def attachment_create(self, author, filename):
" create attachment and add to attachments "
# validate if given filename is valid
if not self.validate_attachment_filename(kwargs['filename'],
kwargs['ext']):
filename, ext = os.path.splitext(filename)
ext = ext.split('.')[1].lower() if ext else ''
if not self.validate_attachment_filename(filename, ext):
raise FilenameExistException(
('Sorry, there is already an attachment in your add-on with '
'the name "%s.%s". Each attachment in your add-on needs to '
'have a unique name.') % (kwargs['filename'], kwargs['ext'])
'have a unique name.') % (filename, ext)
)
att = Attachment.objects.create(**kwargs)
self.attachment_add(att)
att = Attachment.objects.create(filename=filename,
ext=ext, author=author)
att.save()
self.attachment_add(att, check=False)
return att
def attachment_add(self, att):
def attachment_add(self, att, check=True):
" copy to new revision, add attachment "
# save as new version
# validate if given filename is valid
if not self.validate_attachment_filename(att.filename, att.ext):
if (check and
not self.validate_attachment_filename(att.filename, att.ext)):
raise FilenameExistException(
'Attachment with filename %s.%s already exists' % (
att.filename, att.ext)
@ -734,6 +738,18 @@ class PackageRevision(models.Model):
] if self.modules.count() > 0 else []
return simplejson.dumps(m_list)
def get_attachments_list_json(self):
" returns attachments list as JSON object "
a_list = [{
'uid': a.get_uid,
'filename': a.filename,
'author': a.author.username,
'type': a.ext,
'get_url': reverse('jp_attachment', args=[a.get_uid])
} for a in self.attachments.all()
] if self.attachments.count() > 0 else []
return simplejson.dumps(a_list)
def get_sdk_name(self):
" returns the name of the directory to which SDK should be copied "
return '%s-%s-%s' % (self.sdk.version,
@ -922,6 +938,12 @@ class Module(models.Model):
'code': self.code,
'author': self.author.username})
def increment(self, revision):
revision.modules.remove(self)
self.pk = None
self.save()
revision.modules.add(self)
class Attachment(models.Model):
"""
@ -948,6 +970,16 @@ class Attachment(models.Model):
" attachment ordering "
ordering = ('filename',)
@property
def get_uid(self):
"""A uid that contains, filename and extension and is suitable
for use in css selectors (eg: no spaces)."""
return str(int(self.pk))
@property
def is_editable(self):
return self.ext in ["html", "css", "js", "txt"]
def get_filename(self):
" returns human readable filename with extension "
name = self.filename
@ -955,21 +987,52 @@ class Attachment(models.Model):
name = "%s.%s" % (name, self.ext)
return name
def save(self, **kwargs):
" overloaded to prevent from updating "
if self.id:
raise UpdateDeniedException(
'Attachment can not be updated in the same row')
return super(Attachment, self).save(**kwargs)
def get_display_url(self):
"""Returns URL to display the attachment."""
return reverse('jp_attachment', args=[self.get_uid])
def create_path(self):
args = (self.pk, self.filename, self.ext)
self.path = os.path.join(time.strftime('%Y/%m/%d'), '%s-%s.%s' % args)
def get_file_path(self):
if self.path:
return os.path.join(settings.UPLOAD_DIR, self.path)
raise ValueError("self.path not set.")
def read(self):
"""Reads the file, if it doesn't exist return empty."""
if self.path and os.path.exists(self.get_file_path()):
return open(self.get_file_path(), 'rb').read()
return ""
def changed(self):
return not self.read() == self.data
def write(self):
"""Writes the file."""
self.create_path()
self.save()
directory = os.path.dirname(self.get_file_path())
if not os.path.exists(directory):
os.makedirs(directory)
handle = open(self.get_file_path(), 'wb')
handle.write(self.data)
handle.close()
def export_file(self, static_dir):
" copies from uploads to the package's data directory "
shutil.copy('%s/%s' % (settings.UPLOAD_DIR, self.path),
'%s/%s.%s' % (static_dir, self.filename, self.ext))
def get_display_url(self):
" returns URL to display the attachment "
return reverse('jp_attachment', args=[self.path])
def increment(self, revision):
revision.attachments.remove(self)
self.pk = None
self.write()
self.save()
revision.attachments.add(self)
class SDK(models.Model):

Просмотреть файл

@ -10,6 +10,7 @@ def get_package_revision(id_name, type_id,
"""
Return revision of the package
"""
if not (revision_number or version_name):
# get default revision - one linked via Package:version
package = get_object_with_related_or_404(Package, id_number=id_name,

Просмотреть файл

@ -0,0 +1,3 @@
{% if attachment.is_editable %}
<textarea id="{{ attachment.get_uid }}_attachment_textarea" class="UI_Editor_Area" name="{{ attachment.get_uid }}_attachment_textarea">{{ attachment.read }}</textarea>
{% endif %}

Просмотреть файл

@ -1,2 +1 @@
<textarea {% if readonly %}readonly="readonly" {% endif %}id="{{ module.filename }}_textarea" class="UI_Editor_Area" name="{{ module.filename }}_textarea">{{ module.code }}</textarea>

Просмотреть файл

@ -14,8 +14,8 @@
{% block core_library %}
{% if revision.sdk %}
<li class="UI_File_Normal Core_library" id="core_library_lib">
<a title="" href="{{ revision.get_sdk_revision.get_absolute_url }}"
target="{{ revision.get_sdk_revision.package.name }}"
<a title="" href="{{ revision.get_sdk_revision.get_absolute_url }}"
target="{{ revision.get_sdk_revision.package.name }}"
class="library_link">
<span>{{ revision.get_sdk_revision.package.full_name }}</span>
</a>
@ -56,5 +56,7 @@
{% for module in revision.modules.all %}{% if module.filename != revision.module_main %}
{% include "_module_code_textarea.html" %}
{% endif %}{% endfor %}
{% for attachment in revision.attachments.all %}
{% include "_attachment_code_textarea.html" %}
{% endfor %}
{% endblock %}

Просмотреть файл

@ -89,7 +89,7 @@
<ul class="UI_File_Listing" id='attachments_ul'>
{% for attachment in attachments %}
<li class="UI_File_Normal">
<a title="" href="{{ attachment.get_display_url }}" rel="{{ attachment.ext }}" class="Module_file" id="{{ attachment.filename }}{{ attachment.ext }}_display">{{ attachment.get_filename }}<span class="File_close"></span></a>
<a title="" href="{{ attachment.get_display_url }}" rel="{{ attachment.ext }}" class="Module_file" id="{{ attachment.get_uid }}_attachment_switch">{{ attachment.get_filename }}<span class="File_close"></span></a>
</li>
{% endfor %}
</ul>

Просмотреть файл

@ -23,6 +23,7 @@ fd.item = new Package.Edit({
revision_author: '{{ revision.author }}',
switch_sdk_url: '{{ revision.get_switch_sdk_url }}',
modules: {{ revision.get_modules_list_json|safe }},
attachments: {{ revision.get_attachments_list_json|safe }},
// Actions
save_url: '{{ revision.get_save_url }}',
add_module_url: '{{ revision.get_add_module_url }}',

Просмотреть файл

@ -21,6 +21,7 @@ fd.item = new Package.View({
origin_url: '{{ revision.origin.get_absolute_url }}',
revision_author: '{{ revision.author }}',
modules: {{ revision.get_modules_list_json|safe }},
attachments: {{ revision.get_attachments_list_json|safe }},
package_info: '{% escape_template "_view_package_info.html" %}',
copy_url: '{{ revision.get_copy_url }}'
});

Просмотреть файл

@ -6,6 +6,7 @@
"ext": "{{ attachment.ext }}",
"basename": "{{ attachment.get_filename }}",
"display_url": "{{ attachment.get_display_url }}",
"author": "{{ attachment.author.username }}"
"author": "{{ attachment.author.username }}",
"uid": "{{ attachment.get_uid }}",
"get_url": "{% url jp_attachment attachment.get_uid %}"
}

Просмотреть файл

@ -3,7 +3,6 @@
"message_title": "Success",
"message": "Attachment {{ attachment.filename }} removed",
"filename": "{{ attachment.filename }}",
"ext": "{{ attachment.ext }}"
"ext": "{{ attachment.ext }}",
"uid": "{{ attachment.get_uid }}"
}

Просмотреть файл

@ -1,11 +1,11 @@
import os
import tempfile
from test_utils import TestCase
from django.contrib.auth.models import User
from django.conf import settings
from jetpack.models import Attachment
from jetpack.errors import UpdateDeniedException
class AttachmentTest(TestCase):
@ -16,28 +16,27 @@ class AttachmentTest(TestCase):
def setUp(self):
self.author = User.objects.get(username='john')
if not os.path.exists(settings.UPLOAD_DIR):
os.mkdir(settings.UPLOAD_DIR)
self.old = settings.UPLOAD_DIR
settings.UPLOAD_DIR = tempfile.mkdtemp()
# Simulating upload.
handle = open(os.path.join(settings.UPLOAD_DIR, 'test_filename.txt'),
'w')
handle.write('unit test file')
handle.close()
self.attachment = Attachment.objects.create(
filename='test_filename',
ext='txt',
path='test_filename.txt',
filename='test_filename.txt',
author=self.author
)
self.attachment.create_path()
self.attachment.data = 'test'
self.attachment.write()
self.path = self.attachment.path
def tearDown(self):
os.remove(os.path.join(settings.UPLOAD_DIR, 'test_filename.txt'))
def test_update_attachment_using_save(self):
" updating attachment is not allowed "
self.assertRaises(UpdateDeniedException, self.attachment.save)
os.remove(os.path.join(settings.UPLOAD_DIR, self.path))
settings.UPLOAD_DIR = self.old
def test_export_file(self):
self.attachment.export_file('/tmp')
self.failUnless(os.path.isfile('/tmp/test_filename.txt'))
destination = tempfile.mkdtemp()
filename = '%s.%s' % (self.attachment.filename, self.attachment.ext)
filename = os.path.join(destination, filename)
self.attachment.export_file(destination)
self.failUnless(os.path.isfile(filename))

Просмотреть файл

@ -1,5 +1,10 @@
import tempfile
import os
from test_utils import TestCase
from nose.tools import eq_
from django.contrib.auth.models import User
from jetpack.models import Package, PackageRevision, Module, Attachment
@ -178,9 +183,7 @@ class PackageRevisionTest(TestCase):
addon.save()
first = addon.latest
first.attachment_create(
filename='test',
ext='txt',
path='/tmp/testupload',
filename='test.txt',
author=self.author
)
@ -189,8 +192,26 @@ class PackageRevisionTest(TestCase):
first = revisions[1]
second = revisions[0]
self.assertEqual(0, first.attachments.count())
self.assertEqual(1, second.attachments.count())
eq_(0, first.attachments.count())
eq_(1, second.attachments.count())
def test_read_write_attachment(self):
"""Test that we can read and write to an attachment."""
addon = Package(author=self.author, type='a')
addon.save()
first = addon.latest
filename = tempfile.mkstemp()[1]
try:
attachment = first.attachment_create(
filename='test.txt',
author=self.author
)
attachment.data = 'This is a test.'
attachment.write()
assert attachment.read() == attachment.data
assert not attachment.changed()
finally:
os.remove(filename)
def test_updating_module(self):
" Updating module has some additional action "
@ -202,7 +223,7 @@ class PackageRevisionTest(TestCase):
author=self.author
)
mod.code = 'test'
first.module_update(mod)
first.update(mod)
# create new revision on module update
self.assertEqual(3, addon.revisions.count())
@ -233,25 +254,19 @@ class PackageRevisionTest(TestCase):
self.assertRaises(FilenameExistException, first.module_add, mod)
def test_adding_attachment_with_existing_filename(self):
" filname is unique per packagerevision "
"""Filename is unique per packagerevision."""
first = PackageRevision.objects.filter(package__pk=self.addon.pk)[0]
first.attachment_create(
filename='test_filename',
ext='.txt',
path='/tmp/upload_path',
filename='test_filename.txt',
author=self.author
)
self.assertRaises(FilenameExistException, first.attachment_create,
**{'filename': 'test_filename',
'ext': '.txt',
'author': self.author,
'path': '/tmp/upload_path'}
**{'filename': 'test_filename.txt',
'author': self.author}
)
att = Attachment.objects.create(
filename='test_filename',
ext='.txt',
path='/tmp/upload_path',
ext='txt',
author=self.author
)
self.assertRaises(FilenameExistException, first.attachment_add, att)

Просмотреть файл

@ -1,12 +1,30 @@
from django.core.urlresolvers import reverse
import os
import json
import StringIO
from datetime import datetime
import test_utils
from test_utils import TestCase
from nose.tools import eq_
from nose import SkipTest
from mock import patch
from pyquery import PyQuery as pq
from django.conf import settings
from django.contrib.auth.models import User
from django.core.urlresolvers import reverse
class TestViews(test_utils.TestCase):
from jetpack.models import PackageRevision
from jetpack.errors import FilenameExistException
def next(revision):
number = revision.revision_number
return (PackageRevision.objects.filter(revision_number__gt=number)
.order_by('-revision_number')[:1])[0]
class TestViews(TestCase):
fixtures = ('mozilla_user', 'core_sdk', 'users', 'packages')
def setUp(self):
@ -29,3 +47,120 @@ class TestViews(test_utils.TestCase):
r = self.client.get(next)
doc = pq(r.content)
eq_(doc('#app-content h2').text(), 'XPI Not Ready')
class TestAttachments(TestCase):
fixtures = ['mozilla_user', 'users', 'core_sdk', 'packages']
def setUp(self):
if not os.path.exists(settings.UPLOAD_DIR):
os.makedirs(settings.UPLOAD_DIR)
self.author = User.objects.get(username='john')
self.author.set_password('password')
self.author.save()
self.package = self.author.packages_originated.addons()[0:1].get()
self.revision = self.package.revisions.all()[0]
self.add_url = self.get_add_url(self.revision.revision_number)
self.change_url = self.get_change_url(self.revision.revision_number)
self.client.login(username=self.author.username, password='password')
def test_attachment_error(self):
res = self.client.post(self.add_url, {})
eq_(res.status_code, 500)
def get_add_url(self, revision):
args = [self.package.id_number, revision]
return reverse('jp_addon_revision_add_attachment', args=args)
def get_change_url(self, revision):
args = [self.package.id_number, revision]
return reverse('jp_addon_revision_save', args=args)
def get_revision(self):
return PackageRevision.objects.get(pk=self.revision.pk)
def post(self, url, data, filename):
# A post that matches the JS and uses raw_post_data.
return self.client.post(url, data,
content_type='text/plain',
HTTP_X_FILE_NAME=filename)
def test_attachment_path(self):
res = self.post(self.add_url, 'foo', 'some.txt')
eq_(res.status_code, 200)
revision = PackageRevision.objects.get(package=self.package,
revision_number=1)
att = revision.attachments.all()[0]
bits = att.path.split(os.path.sep)
now = datetime.now()
eq_(bits[-4:-1], [str(now.year), str(now.month), str(now.day)])
def test_attachment_add_read(self):
res = self.post(self.add_url, 'foo', 'some.txt')
eq_(res.status_code, 200)
revision = PackageRevision.objects.get(package=self.package,
revision_number=1)
eq_(revision.attachments.count(), 1)
eq_(revision.attachments.all()[0].read(), 'foo')
def test_attachment_add(self):
res = self.post(self.add_url, 'foo', 'some.txt')
eq_(res.status_code, 200)
json.loads(res.content)
revision = PackageRevision.objects.get(package=self.package,
revision_number=1)
eq_(revision.attachments.count(), 1)
def test_attachment_large(self):
raise SkipTest()
# A test for large attachments... really slow things
# down, so before you remove the above, clean this up
# or drop down the file size limit.
temp = StringIO.StringIO()
for x in range(0, 1024 * 32):
temp.write("x" * 1024)
self.post(self.add_url, temp.getvalue(), 'some-big-file.txt')
def test_attachment_same_fails(self):
self.test_attachment_add()
self.assertRaises(FilenameExistException, self.post,
self.get_add_url(1), 'foo bar', 'some.txt')
def test_attachment_revision_count(self):
revisions = PackageRevision.objects.filter(package=self.package)
eq_(revisions.count(), 1)
self.test_attachment_add()
eq_(revisions.count(), 2)
# Double check that adding a revision does not create a new version.
def test_attachment_same_change(self):
self.test_attachment_add()
revision = PackageRevision.objects.get(package=self.package,
revision_number=1)
eq_(revision.attachments.count(), 1)
data = {revision.attachments.all()[0].get_uid: 'foo bar'}
res = self.client.post(self.get_change_url(1), data)
eq_(res.status_code, 200)
eq_(revision.attachments.all()[0].read(), 'foo')
revision = PackageRevision.objects.get(package=self.package,
revision_number=2)
eq_(revision.attachments.count(), 1)
eq_(revision.attachments.all()[0].read(), 'foo bar')
def test_attachment_two_files(self):
self.post(self.add_url, 'foo', 'some.txt')
revision = PackageRevision.objects.get(package=self.package,
revision_number=1)
assert revision.attachments.count(), 1
self.post(self.add_url, 'foo', 'some-other.txt')
assert revision.attachments.count(), 2

Просмотреть файл

@ -113,7 +113,7 @@ urlpatterns = patterns('jetpack.views',
{'type_id': 'l'}, name='jp_library_revision_remove_attachment'),
# display attachment
url(r'^attachment/(?P<path>.*)$',
url(r'^attachment/(?P<uid>.*)$',
'download_attachment', name='jp_attachment'),
# autocomplete library

Просмотреть файл

@ -1,8 +1,6 @@
"""
Views for the Jetpack application
"""
import os
import time
import commonware.log
from django.contrib import messages
@ -81,6 +79,7 @@ def package_view_or_edit(r, id_number, type_id, revision_number=None,
else:
return package_view(r, revision)
@login_required
def package_edit(r, revision):
"""
@ -179,8 +178,6 @@ def package_copy(r, id_number, type_id,
escape(source.package.get_type_name()))
@login_required
def package_disable(r, id_number):
"""
@ -344,31 +341,17 @@ def package_add_attachment(r, id_number, type_id,
% escape(revision.package.get_type_name()))
content = r.raw_post_data
path = r.META.get('HTTP_X_FILE_NAME', False)
filename = r.META.get('HTTP_X_FILE_NAME')
if not path:
if not filename:
log_msg = 'Path not found: %s, package: %s.' % (
path, id_number)
filename, id_number)
log.debug(log_msg)
return HttpResponseServerError
return HttpResponseServerError('Path not found.')
filename, ext = os.path.splitext(path)
ext = ext.split('.')[1].lower() if ext else ''
upload_path = "%s_%s_%s.%s" % (revision.package.id_number,
time.strftime("%m-%d-%H-%M-%S"),
filename, ext)
handle = open(os.path.join(settings.UPLOAD_DIR, upload_path), 'w')
handle.write(content)
handle.close()
attachment = revision.attachment_create(
author=r.user,
filename=filename,
ext=ext,
path=upload_path
)
attachment = revision.attachment_create(r.user, filename)
attachment.data = content
attachment.write()
return render_to_response("json/attachment_added.json",
{'revision': revision, 'attachment': attachment},
@ -416,13 +399,14 @@ def package_remove_attachment(r, id_number, type_id, revision_number):
mimetype='application/json')
def download_attachment(r, path):
def download_attachment(request, uid):
"""
Display attachment from PackageRevision
"""
get_object_or_404(Attachment, path=path)
response = serve(r, path, settings.UPLOAD_DIR, show_indexes=False)
#response['Content-Type'] = 'application/octet-stream';
attachment = get_object_or_404(Attachment, id=uid)
response = serve(request, attachment.path,
settings.UPLOAD_DIR, show_indexes=False)
response['Content-Disposition'] = 'filename=%s' % attachment.filename
return response
@ -482,16 +466,23 @@ def package_save(r, id_number, type_id, revision_number=None,
revision.package.description = package_description
response_data['package_description'] = package_description
modules = []
changes = []
for mod in revision.modules.all():
if r.POST.get(mod.filename, False):
code = r.POST[mod.filename]
if mod.code != code:
mod.code = code
modules.append(mod)
changes.append(mod)
if modules:
revision.modules_update(modules)
for attachment in revision.attachments.all():
if attachment.get_uid in r.POST:
data = r.POST[attachment.get_uid]
attachment.data = data
if attachment.changed():
changes.append(attachment)
if changes:
revision.updates(changes)
save_revision = False
if save_revision:
@ -574,7 +565,6 @@ def library_autocomplete(r):
except:
found = []
return render_to_response('json/library_autocomplete.json',
{'libraries': found},
context_instance=RequestContext(r),
@ -663,5 +653,3 @@ def get_revisions_list_html(r, id_number):
# ---------------------------- XPI ---------------------------------

Просмотреть файл

@ -121,18 +121,19 @@ class XPIBuildTest(TestCase):
self.addonrev.module_main)))
def test_addon_export_with_attachment(self):
" test if attachment file is coped "
"""Test if attachment file is copied."""
self.makeSDKDir()
# create attachment in upload dir
handle = open(self.attachment_file_name, 'w')
handle.write('unit test file')
handle.close()
self.addonrev.attachment_create(
filename='test_filename',
ext='txt',
path='test_filename.txt',
attachment = self.addonrev.attachment_create(
filename='test_filename.txt',
author=self.author
)
attachment.create_path()
attachment.data = ''
attachment.write()
self.addonrev.export_files_with_dependencies(
'%s/packages' % self.SDKDIR)
self.failUnless(os.path.isfile(self.attachment_file_name))

Разница между файлами не показана из-за своего большого размера Загрузить разницу

Просмотреть файл

@ -42,8 +42,8 @@ sendFile = (function(toString, maxSize){
} else {
setTimeout(function(){
if(xhr.readyState === 4){
if(isFunction(handler.onpartialload))
handler.onpartialload(rpe, xhr);
if(isFunction(handler.onpartialload))
handler.onpartialload(rpe, xhr);
if(isFunction(handler.onload))
handler.onload(rpe, xhr);
} else
@ -112,12 +112,12 @@ if(isset($_GET['upload']) && $_GET['upload'] === 'true'){
$file->name = basename($headers['X-File-Name']);
$file->size = $headers['X-File-Size'];
$file->content = file_get_contents("php://input");
// if everything is ok, save the file somewhere
if(file_put_contents('files/'.$file->name, $file->content))
exit('OK');
}
// if there is an error this will be the output instead of "OK"
exit('Error');
}