зеркало из https://github.com/mozilla/FlightDeck.git
Merge pull request #136 from seanmonstar/custom-package-properties
Custom Package Properties
This commit is contained in:
Коммит
c8648edb1d
|
@ -144,6 +144,9 @@ class PackageRevision(BaseModel):
|
|||
#: SDK which should be used to create the XPI
|
||||
sdk = models.ForeignKey('SDK', blank=True, null=True)
|
||||
|
||||
#: Extra package.json properties
|
||||
extra_json = models.TextField(blank=True)
|
||||
|
||||
class Meta:
|
||||
" PackageRevision ordering and uniqueness "
|
||||
ordering = ('-revision_number',)
|
||||
|
@ -528,8 +531,12 @@ class PackageRevision(BaseModel):
|
|||
name = self.name
|
||||
#if not self.package.is_addon():
|
||||
# name = "%s-%s" % (name, self.package.id_number)
|
||||
try:
|
||||
manifest = simplejson.loads(self.extra_json)
|
||||
except simplejson.JSONDecodeError:
|
||||
manifest = {}
|
||||
|
||||
manifest = {
|
||||
manifest.update({
|
||||
'fullName': self.full_name,
|
||||
'name': name,
|
||||
'description': escape(self.package.description),
|
||||
|
@ -543,7 +550,7 @@ class PackageRevision(BaseModel):
|
|||
'url': str(self.package.url),
|
||||
'contributors': self.get_contributors_list(),
|
||||
'lib': self.get_lib_dir()
|
||||
}
|
||||
})
|
||||
if (self.package.is_library()
|
||||
and waffle.switch_is_active(
|
||||
'LibDirInMainAttributeWorkaround')):
|
||||
|
@ -778,6 +785,24 @@ class PackageRevision(BaseModel):
|
|||
|
||||
return super(PackageRevision, self).save()
|
||||
|
||||
def set_extra_json(self, extra_json, save=True):
|
||||
"""
|
||||
Sets self.extra_json, adds commit message, and saves revision
|
||||
by default. Pass save=False if you will save later.
|
||||
|
||||
raises JSONDecodeError
|
||||
"""
|
||||
self.add_commit_message('Extra JSON properties changed')
|
||||
if extra_json:
|
||||
# if not an empty string or None, just check it is
|
||||
# valid JSON
|
||||
simplejson.loads(extra_json)
|
||||
|
||||
self.extra_json = extra_json
|
||||
if save:
|
||||
self.save()
|
||||
|
||||
|
||||
def validate_module_filename(self, filename):
|
||||
"""
|
||||
returns False if the package revision contains a module with given
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
<form class="UI_Forms" id="package-info_form" method="post" accept-charset="utf-8">
|
||||
<h3>Edit {full_name} info</h3>
|
||||
<h3>Edit #{full_name} info</h3>
|
||||
<fieldset>
|
||||
<label class="UI_Field">
|
||||
<span>{{ revision.package.get_type_name_with_dash()|capfirst }} Name</span>
|
||||
<input type="text" name="full_name" id="full_name" class="validate-alphanum_plus_space" value="{full_name}"/>
|
||||
<input type="text" name="full_name" id="full_name" class="validate-alphanum_plus_space" value="#{full_name}"/>
|
||||
</label>
|
||||
<label class="UI_Field">
|
||||
<span>{{ revision.package.get_type_name_with_dash()|capfirst }} Description</span>
|
||||
|
@ -15,6 +15,10 @@
|
|||
{% include "_package_privacy_toggle.html" %}
|
||||
</ul>
|
||||
</label>
|
||||
<label class="UI_Field ace_editor_container">
|
||||
<span>Extra package.json Properties</span>
|
||||
<textarea id="package_extra_json" name="package_extra_json" rows="8" cols="40">{{revision.extra_json}}</textarea>
|
||||
</label>
|
||||
</fieldset>
|
||||
<div class="UI_Modal_Actions">
|
||||
<ul>
|
||||
|
|
|
@ -354,6 +354,47 @@ class PackageRevisionTest(TestCase):
|
|||
)
|
||||
self.assertRaises(FilenameExistException, first.attachment_add, att)
|
||||
|
||||
def test_adding_extra_package_properties(self):
|
||||
addon = Package(type='a', author=self.author)
|
||||
addon.save()
|
||||
pk = addon.pk
|
||||
rev = addon.latest
|
||||
|
||||
rev.set_extra_json('''
|
||||
{
|
||||
"preferences": [{
|
||||
"name": "example",
|
||||
"type": "string",
|
||||
"title": "foo",
|
||||
"value": "bar"
|
||||
}],
|
||||
"id": "baz"
|
||||
}
|
||||
''')
|
||||
|
||||
addon = Package.objects.get(pk=pk) # breaking cache
|
||||
manifest = addon.latest.get_manifest()
|
||||
assert 'preferences' in manifest
|
||||
eq_(manifest['preferences'][0]['name'], 'example')
|
||||
|
||||
# user-provide values don't override our generated ones
|
||||
self.assertNotEqual(manifest['id'], 'baz')
|
||||
|
||||
assert 'Extra JSON' in addon.latest.commit_message
|
||||
|
||||
def test_adding_invalid_extra_json(self):
|
||||
addon = Package(type='a', author=self.author)
|
||||
addon.save()
|
||||
pk = addon.pk
|
||||
rev = addon.latest
|
||||
|
||||
from simplejson import JSONDecodeError
|
||||
self.assertRaises(JSONDecodeError, rev.set_extra_json, '''
|
||||
{
|
||||
foo: baz
|
||||
}
|
||||
''')
|
||||
|
||||
def test_add_commit_message(self):
|
||||
author = User.objects.all()[0]
|
||||
addon = Package(type='a', author=author)
|
||||
|
|
|
@ -236,6 +236,13 @@ class TestEditing(TestCase):
|
|||
def setUp(self):
|
||||
self.hashtag = hashtag()
|
||||
|
||||
def _login(self):
|
||||
self.author = User.objects.get(username='jan')
|
||||
self.author.set_password('test')
|
||||
self.author.save()
|
||||
self.client.login(username=self.author.username, password='test')
|
||||
return self.author
|
||||
|
||||
def test_revision_list_contains_added_modules(self):
|
||||
author = User.objects.get(username='john')
|
||||
addon = Package(author=author, type='a')
|
||||
|
@ -251,13 +258,10 @@ class TestEditing(TestCase):
|
|||
assert 'test_filename' in r.content
|
||||
|
||||
def test_package_name_change(self):
|
||||
author = User.objects.get(username='jan')
|
||||
author.set_password('secure')
|
||||
author.save()
|
||||
author = self._login()
|
||||
addon1 = Package(author=author, type='a')
|
||||
addon1.save()
|
||||
rev1 = addon1.latest
|
||||
self.client.login(username=author.username, password='secure')
|
||||
response = self.client.post(addon1.latest.get_save_url(), {
|
||||
'full_name': 'FULL NAME'})
|
||||
eq_(response.status_code, 200)
|
||||
|
@ -266,6 +270,51 @@ class TestEditing(TestCase):
|
|||
eq_(addon2.full_name, addon2.latest.full_name)
|
||||
assert rev1.name != addon2.latest.name
|
||||
|
||||
def test_package_extra_json_change(self):
|
||||
author = self._login()
|
||||
addon = Package(author=author, type='a')
|
||||
addon.save()
|
||||
pk = addon.pk
|
||||
|
||||
homepage = 'https://builder.addons.mozilla.org'
|
||||
extra_json = '{ "homepage": "%s" }' % homepage
|
||||
response = self.client.post(addon.latest.get_save_url(), {
|
||||
'package_extra_json': extra_json})
|
||||
|
||||
addon = Package.objects.get(pk=pk) # old one is cached
|
||||
|
||||
eq_(addon.latest.extra_json, extra_json)
|
||||
|
||||
def test_package_remove_extra_json(self):
|
||||
author = self._login()
|
||||
addon = Package(author=author, type='a')
|
||||
addon.save()
|
||||
pk = addon.pk
|
||||
|
||||
homepage = 'https://builder.addons.mozilla.org'
|
||||
extra_json = '{ "homepage": "%s" }' % homepage
|
||||
addon.latest.extra_json = extra_json
|
||||
addon.latest.save()
|
||||
|
||||
response = self.client.post(addon.latest.get_save_url(), {
|
||||
'package_extra_json': ''})
|
||||
|
||||
addon = Package.objects.get(pk=pk) # old on is cached
|
||||
|
||||
eq_(addon.latest.extra_json, '')
|
||||
|
||||
def test_package_invalid_extra_json(self):
|
||||
author = self._login()
|
||||
addon = Package(author=author, type='a')
|
||||
addon.save()
|
||||
|
||||
extra_json = '{ foo: bar }'
|
||||
response = self.client.post(addon.latest.get_save_url(), {
|
||||
'package_extra_json': extra_json})
|
||||
|
||||
eq_(response.status_code, 400)
|
||||
assert 'invalid JSON' in response.content
|
||||
|
||||
|
||||
class TestRevision(TestCase):
|
||||
fixtures = ('mozilla_user', 'core_sdk', 'users', 'packages')
|
||||
|
|
|
@ -7,6 +7,7 @@ import shutil
|
|||
import codecs
|
||||
import tempfile
|
||||
import urllib2
|
||||
from simplejson import JSONDecodeError
|
||||
|
||||
from django.contrib import messages
|
||||
from django.core.urlresolvers import reverse
|
||||
|
@ -900,6 +901,19 @@ def save(request, id_number, type_id, revision_number=None,
|
|||
revision.package.description = package_description
|
||||
response_data['package_description'] = package_description
|
||||
|
||||
extra_json = request.POST.get('package_extra_json')
|
||||
if extra_json is not None:
|
||||
# None means it wasn't submitted. We want to accept blank strings.
|
||||
save_revision = True
|
||||
log.debug('extra_json: %s' % extra_json)
|
||||
try:
|
||||
revision.set_extra_json(extra_json, save=False)
|
||||
except JSONDecodeError:
|
||||
return HttpResponseBadRequest(
|
||||
'Extra package properties were invalid JSON.')
|
||||
response_data['package_extra_json'] = extra_json
|
||||
|
||||
|
||||
changes = []
|
||||
for mod in revision.modules.all():
|
||||
if request.POST.get(mod.filename, False):
|
||||
|
|
|
@ -287,3 +287,25 @@ authors:
|
|||
width:69%;
|
||||
padding:0 5px;
|
||||
}
|
||||
|
||||
.UI_Modal .UI_Field.ace_editor_container {
|
||||
position:relative;
|
||||
}
|
||||
.UI_Modal .UI_Field.ace_editor_container .ace_editor {
|
||||
height:100px;
|
||||
position:relative;
|
||||
float:right;
|
||||
width: 70%;
|
||||
}
|
||||
|
||||
.UI_Modal .UI_Field.ace_editor_container .ace_editor .ace_gutter {
|
||||
width:15px;
|
||||
}
|
||||
|
||||
|
||||
.UI_Modal .UI_Field.ace_editor_container textarea {
|
||||
width:10px;
|
||||
height:30px;
|
||||
float:none;
|
||||
padding:0;
|
||||
}
|
||||
|
|
|
@ -18,6 +18,7 @@ var Class = require('shipyard/class/Class'),
|
|||
|
||||
FloatingTips = require('../views/FloatingTips'),
|
||||
Validator = require('../views/Validator'),
|
||||
JSONValidator = require('../views/JSONValidator'),
|
||||
|
||||
//TODO: this is bad practice
|
||||
settings = dom.window.get('settings');
|
||||
|
@ -48,7 +49,11 @@ module.exports = new Class({
|
|||
save_el: 'package-save',
|
||||
menu_el: 'UI_Editor_Menu',
|
||||
|
||||
package_info_form_elements: ['full_name', 'package_description'],
|
||||
package_info_form_elements: [
|
||||
'full_name',
|
||||
'package_description',
|
||||
'package_extra_json'
|
||||
],
|
||||
|
||||
check_dependencies: true,
|
||||
check_if_latest: true // switch to false if displaying revisions
|
||||
|
@ -1328,7 +1333,8 @@ module.exports = new Class({
|
|||
this.savenow = false;
|
||||
var modal = fd().editPackageInfoModal = fd().displayModal(
|
||||
string.substitute(settings.edit_package_info_template,
|
||||
object.merge({}, this.data, this.options)));
|
||||
object.merge({}, this.data, this.options),
|
||||
/#\{([^{}]+)\}/g));
|
||||
|
||||
modal.addListener('destroy', function() {
|
||||
delete fd().editPackageInfoModal;
|
||||
|
@ -1339,6 +1345,8 @@ module.exports = new Class({
|
|||
dom.$('package_description').addListener('change', function() {
|
||||
fd().emit('change');
|
||||
});
|
||||
|
||||
|
||||
var savenow = dom.$('savenow');
|
||||
if (savenow) {
|
||||
savenow.addListener('click', function() {
|
||||
|
@ -1362,16 +1370,31 @@ module.exports = new Class({
|
|||
pressedBtn.addClass('pressed').getElement('a').addClass('inactive');
|
||||
notPressedBtn.removeClass('pressed').getElement('a').removeClass('inactive');
|
||||
|
||||
var validator = new Validator('full_name', {
|
||||
var fullNameValidator = new Validator('full_name', {
|
||||
pattern: /^[A-Za-z0-9\s\-_\.\(\)]*$/,
|
||||
message: 'Please use only letters, numbers, spaces, or "_().-" in this field.'
|
||||
});
|
||||
var jsonValidator = new JSONValidator('package_extra_json', {
|
||||
message: 'Must be blank or a valid JSON object.'
|
||||
});
|
||||
|
||||
var package_extra_json_el = dom.$('package_extra_json');
|
||||
package_extra_json_el.store('json-validator', jsonValidator);
|
||||
if ('package_extra_json' in this.options) {
|
||||
package_extra_json_el.set('value', this.options.package_extra_json);
|
||||
}
|
||||
|
||||
this._setupExtraPropertiesEditor();
|
||||
|
||||
function isValid() {
|
||||
return fullNameValidator.validate() && jsonValidator.validate();
|
||||
}
|
||||
dom.$('package-info_form').addListener('submit', function(e) {
|
||||
e.stop();
|
||||
if (validator.validate()) {
|
||||
if (isValid()) {
|
||||
controller.submitInfo();
|
||||
} else {
|
||||
log.debug('Form field full_name field has invalid characters.');
|
||||
log.debug('Invalid name or JSON.');
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -1384,6 +1407,34 @@ module.exports = new Class({
|
|||
});
|
||||
},
|
||||
|
||||
_setupExtraPropertiesEditor: function() {
|
||||
// Make package.json textarea an ACE editor
|
||||
var ace = require('ace/ace');
|
||||
|
||||
var extra_json_el = dom.$('package_extra_json');
|
||||
extra_json_el.setStyle('display', 'none');
|
||||
var extra_json_editor_el = new dom.Element('div', {
|
||||
id: 'extra_json_ace',
|
||||
text: extra_json_el.get('value')
|
||||
});
|
||||
extra_json_editor_el.inject(extra_json_el, 'before');
|
||||
var editor = ace.edit(extra_json_editor_el.getNode());
|
||||
var editorSession = editor.getSession();
|
||||
editorSession.on('change', function() {
|
||||
extra_json_el.set('value', editorSession.getValue());
|
||||
fd().emit('change');
|
||||
});
|
||||
|
||||
var validator = extra_json_el.retrieve('json-validator');
|
||||
editor.on('blur', function() {
|
||||
if (!validator.validate()) {
|
||||
validator.show();
|
||||
} else {
|
||||
validator.hide();
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
/*
|
||||
* Method: submitInfo
|
||||
* submit info from EditInfoModalWindow
|
||||
|
|
|
@ -31,6 +31,7 @@ var Package = module.exports = new Class({
|
|||
revision_number: fields.NumberField(),
|
||||
view_url: fields.TextField(),
|
||||
active: fields.BooleanField(),
|
||||
extra_json: fields.TextField(),
|
||||
|
||||
latest: fields.NumberField() // a FK to PackageRevision
|
||||
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
var Class = require('shipyard/class/Class'),
|
||||
typeOf = require('shipyard/utils/type').typeOf,
|
||||
Validator = require('./Validator');
|
||||
|
||||
module.exports = new Class({
|
||||
|
||||
Extends: Validator,
|
||||
|
||||
validate: function validate() {
|
||||
var text = this.target.get('value').trim();
|
||||
if (text) {
|
||||
try {
|
||||
var json = JSON.parse(text);
|
||||
// number, string, array, etc are illegal.
|
||||
return typeOf(json) === 'object';
|
||||
} catch (jsonError) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
});
|
|
@ -10,7 +10,12 @@ module.exports = new Class({
|
|||
|
||||
options: {
|
||||
pattern: /.*/,
|
||||
message: 'Illegal characters found.'
|
||||
messageTarget: null,
|
||||
message: 'Illegal characters found.',
|
||||
messageStyles: {
|
||||
'visibility': 'visible',
|
||||
'position': 'static'
|
||||
}
|
||||
},
|
||||
|
||||
initialize: function Validator(element, options) {
|
||||
|
@ -42,37 +47,39 @@ module.exports = new Class({
|
|||
return this.options.pattern.test(value);
|
||||
},
|
||||
|
||||
createElement: function() {
|
||||
var messageTarget = dom.$(this.getOption('messageTarget')) || this.target;
|
||||
this.element = new dom.Element('div', {
|
||||
'class': 'validation-advice',
|
||||
'text': this.getOption('message'),
|
||||
'styles': {
|
||||
'height': 0,
|
||||
'display': 'block',
|
||||
'overflow': 'hidden',
|
||||
'opacity': 0
|
||||
}
|
||||
});
|
||||
|
||||
//measure the height
|
||||
this.element.setStyles({
|
||||
'visibility': 'hidden',
|
||||
'position': 'absolute'
|
||||
});
|
||||
this.element.inject(messageTarget, 'after');
|
||||
this.height = this.element.getHeight();
|
||||
this.element.dispose().setStyles(this.getOption('messageStyles'));
|
||||
|
||||
this.anim = new Anim(this.element, {
|
||||
transition: Sine
|
||||
});
|
||||
},
|
||||
|
||||
show: function show() {
|
||||
var validator = this;
|
||||
|
||||
if (!this.element) {
|
||||
this.element = new dom.Element('div', {
|
||||
'class': 'validation-advice',
|
||||
'text': this.getOption('message'),
|
||||
'styles': {
|
||||
'height': 0,
|
||||
'display': 'block',
|
||||
'overflow': 'hidden',
|
||||
'opacity': 0
|
||||
}
|
||||
});
|
||||
|
||||
//measure the height
|
||||
this.element.setStyles({
|
||||
'visibility': 'hidden',
|
||||
'position': 'absolute'
|
||||
});
|
||||
this.element.inject(validator.target, 'after');
|
||||
this.height = this.element.getHeight();
|
||||
this.element.dispose().setStyles({
|
||||
visibility: 'visible',
|
||||
position: 'static'
|
||||
});
|
||||
|
||||
this.anim = new Anim(this.element, {
|
||||
transition: Sine
|
||||
});
|
||||
}
|
||||
this.createElement();
|
||||
}
|
||||
|
||||
this.anim.once('start', function() {
|
||||
validator.element.inject(validator.target, 'after');
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
ALTER TABLE `jetpack_packagerevision` ADD COLUMN `extra_json` TEXT;
|
Загрузка…
Ссылка в новой задаче