Implement editing of layers

Allow users with publish permission to edit any layer, and users with
the same email address as one of the maintainers of a layer to edit that
layer.

Signed-off-by: Paul Eggleton <paul.eggleton@linux.intel.com>
This commit is contained in:
Paul Eggleton 2013-02-27 10:20:24 +00:00
parent 1e3f451139
commit c40bfedd4a
7 changed files with 94 additions and 188 deletions

2
TODO
View File

@ -15,6 +15,7 @@ TODO:
* Show unpublished layers in a different style in the dependency list?
Later:
* Allow adding/editing notes
* Usage links in list page?
* Avoid page content changing size depending on whether scrollbar is there or not?
* Style/extend about page?
@ -22,7 +23,6 @@ Later:
* Style machine list on detail
* Provide a delete function for unpublished layers?
* Show count of layers to be reviewed next to review button
* Ability for users to edit existing layers
* Something to help with compatibility (although maybe this should just be handled using the existing versioned layer dependencies in layer.conf)
* Query backend service? i.e. special URL to query information for external apps/scripts
* Tool for finding/comparing duplicate recipes?

View File

@ -26,10 +26,15 @@
<div class="row-fluid">
<div class="page-header">
<h1>{{ layeritem.name }}
{% if user.is_authenticated and perms.layeritem.publish_layer %}
{% if layeritem.status = "N" %}
<a href="{% url publish layeritem.name %}" class="btn btn-primary pull-right">Publish layer</a>
{% if user.is_authenticated %}
<span class="pull-right">
{% if perms.layeritem.publish_layer or useredit %}
<a href="{% url edit_layer layeritem.name %}" class="btn">Edit layer</a>
{% endif %}
{% if layeritem.status = "N" and perms.layeritem.publish_layer %}
<a href="{% url publish layeritem.name %}" class="btn btn-primary">Publish layer</a>
{% endif %}
</span>
{% endif %}
</h1>
</div>

View File

@ -40,12 +40,20 @@ LayerMaintainerFormSet = inlineformset_factory(LayerItem, LayerMaintainer, form=
class SubmitLayerForm(forms.ModelForm):
# Additional form fields
deps = forms.ModelMultipleChoiceField(label='Other layers this layer depends upon', queryset=LayerItem.objects.all(), required=False, initial=[l.pk for l in LayerItem.objects.filter(name='openembedded-core')])
deps = forms.ModelMultipleChoiceField(label='Other layers this layer depends upon', queryset=LayerItem.objects.all(), required=False)
class Meta:
model = LayerItem
fields = ('name', 'layer_type', 'summary', 'description', 'vcs_url', 'vcs_subdir', 'vcs_web_url', 'vcs_web_tree_base_url', 'vcs_web_file_base_url', 'usage_url', 'mailing_list_url')
def __init__(self, *args, **kwargs):
super(self.__class__, self).__init__(*args, **kwargs)
if self.instance.pk:
self.fields['deps'].initial = [d.dependency.pk for d in self.instance.dependencies_set.all()]
else:
self.fields['deps'].initial = [l.pk for l in LayerItem.objects.filter(name='openembedded-core')]
self.was_saved = False
def checked_deps(self):
val = [int(v) for v in self['deps'].value()]
return val

View File

@ -75,6 +75,13 @@ class LayerItem(models.Model):
def active_maintainers(self):
return self.layermaintainer_set.filter(status='A')
def user_can_edit(self, user):
if user.is_authenticated():
for maintainer in self.active_maintainers():
if maintainer.email == user.email:
return True
return False
def __unicode__(self):
return self.name

View File

@ -1,5 +1,4 @@
{% extends "base.html" %}
{% load i18n %}
{% extends "layerindex/editlayer.html" %}
{% comment %}
@ -20,157 +19,11 @@
<li><a href="#">Submit layer</a></li>
{% endblock %}
{% block content %}
{% autoescape on %}
{% block formtag %}
<form action="{% url submit_layer %}" method="post">
{% csrf_token %}
{% for hidden in form.hidden_fields %}
{{ hidden }}
{% endfor %}
{% endblock %}
{% for field in form.visible_fields %}
<div class="fieldWrapper">
{{ field.errors }}
{{ field.label_tag }}
{% if field.name = 'deps' %}
<div class="scrolling">
<table><tbody>
{% for deplayer in deplistlayers %}
{% if deplayer.id in form.checked_deps %}
<tr>
<td><input type="checkbox" name="deps" value="{{ deplayer.id }}" id="id_deps_{{forloop.counter}}" checked="checked" /></td>
{% if deplayer.status = 'N' %}
<td><label class="muted" for="id_deps_{{forloop.counter}}">{{ deplayer.name }} (new)</label></td>
{% else %}
<td><label for="id_deps_{{forloop.counter}}">{{ deplayer.name }}</label></td>
{% endif %}
</tr>
{% endif %}
{% endfor %}
{% for deplayer in deplistlayers %}
{% if not deplayer.id in form.checked_deps %}
<tr>
<td><input type="checkbox" name="deps" value="{{ deplayer.id }}" id="id_deps_{{forloop.counter}}" /></td>
{% if deplayer.status = 'N' %}
<td><label class="muted" for="id_deps_{{forloop.counter}}">{{ deplayer.name }} (new)</label></td>
{% else %}
<td><label for="id_deps_{{forloop.counter}}">{{ deplayer.name }}</label></td>
{% endif %}
</tr>
{% endif %}
{% endfor %}
</tbody></table>
</div>
{% else %}
{{ field }}
{% endif %}
{{ field.help_text }}
</div>
{% endfor %}
<h3>Maintainers</h3>
{{ maintainerformset.non_form_errors }}
{{ maintainerformset.management_form }}
{% for maintainerform in maintainerformset %}
<h4>Maintainer {{forloop.counter}}</h4>
{% for hidden in maintainerform.hidden_fields %}
{{ hidden }}
{% endfor %}
{% for field in maintainerform.visible_fields %}
<div class="fieldWrapper">
{{ field.errors }}
{{ field.label_tag }}
{{ field }}
{{ field.help_text }}
</div>
{% endfor %}
{% endfor %}
{% block submitbuttons %}
<input type="submit" value="Submit" class='btn' />
</form>
{% endautoescape %}
{% endblock %}
{% block scripts %}
<script>
if (typeof String.prototype.startsWith != 'function') {
String.prototype.startsWith = function (str){
return this.slice(0, str.length) == str;
};
}
auto_web_fields = function (e) {
repoval = $('#id_vcs_url').val()
if( repoval[repoval.length-1] == '/' )
repoval = repoval.slice(0, repoval.length-1)
if( repoval.startsWith('git://git.openembedded.org/') ) {
reponame = repoval.replace(/^.*\//, '')
$('#id_vcs_web_url').val('http://cgit.openembedded.org/cgit.cgi/' + reponame)
$('#id_vcs_web_tree_base_url').val('http://cgit.openembedded.org/cgit.cgi/' + reponame + '/tree/')
$('#id_vcs_web_file_base_url').val('http://cgit.openembedded.org/cgit.cgi/' + reponame + '/tree/')
}
else if( repoval.indexOf('git.yoctoproject.org/') > -1 ) {
reponame = repoval.replace(/^.*\//, '')
$('#id_vcs_web_url').val('http://git.yoctoproject.org/cgit/cgit.cgi/' + reponame)
$('#id_vcs_web_tree_base_url').val('http://git.yoctoproject.org/cgit/cgit.cgi/' + reponame + '/tree/')
$('#id_vcs_web_file_base_url').val('http://git.yoctoproject.org/cgit/cgit.cgi/' + reponame + '/tree/')
}
else if( repoval.indexOf('github.com/') > -1 ) {
reponame = repoval.replace(/^.*github.com\//, '')
reponame = reponame.replace(/.git$/, '')
$('#id_vcs_web_url').val('http://github.com/' + reponame)
$('#id_vcs_web_tree_base_url').val('http://github.com/' + reponame + '/tree/master/')
$('#id_vcs_web_file_base_url').val('http://github.com/' + reponame + '/blob/master/')
}
else if( repoval.indexOf('gitorious.org/') > -1 ) {
reponame = repoval.replace(/^.*gitorious.org\//, '')
reponame = reponame.replace(/.git$/, '')
$('#id_vcs_web_url').val('http://gitorious.org/' + reponame)
$('#id_vcs_web_tree_base_url').val('http://gitorious.org/' + reponame + '/trees/master/')
$('#id_vcs_web_file_base_url').val('http://gitorious.org/' + reponame + '/blobs/master/')
}
else if( repoval.indexOf('bitbucket.org/') > -1 ) {
reponame = repoval.replace(/^.*bitbucket.org\//, '')
reponame = reponame.replace(/.git$/, '')
$('#id_vcs_web_url').val('http://bitbucket.org/' + reponame)
$('#id_vcs_web_tree_base_url').val('http://bitbucket.org/' + reponame + '/src/master/%path%?at=master')
$('#id_vcs_web_file_base_url').val('http://bitbucket.org/' + reponame + '/src/master/%path%?at=master')
}
};
split_email = function() {
// Split email name/email address pairs
name_input = $(this)
split_regex = /^"?([^"@$<>]+)"? *<([^<> ]+)>[ -]*(.*)?$/
matches = split_regex.exec(name_input.val())
if( matches ){
name_input.val($.trim(matches[1]))
email_id = name_input.attr('id').replace('-name', '-email')
$('#' + email_id).val($.trim(matches[2]))
resp_id = email_id.replace('-email', '-responsibility')
currval = $('#' + resp_id).val()
// Set the responsibility with the remainder of the value unless the user has entered a value for
// responsibility already
if( currval == window['last_' + resp_id] || currval == "" ) {
newval = $.trim(matches[3])
$('#' + resp_id).val(newval)
window['last_' + resp_id] = newval
}
}
}
for(i=0;i<{{ maintainerformset.total_form_count }};i++) {
name_input = $('#id_layermaintainer_set-' + i + '-name')
name_input.change(split_email)
resp_id = 'id_layermaintainer_set-' + i + '-responsibility'
window['last_' + resp_id] = ""
}
$(document).ready(function() {
$('#id_vcs_url').change(auto_web_fields)
});
</script>
{% endblock %}

View File

@ -7,14 +7,15 @@
from django.conf.urls.defaults import *
from django.views.generic import DetailView, ListView
from layerindex.models import LayerItem, Recipe
from layerindex.views import LayerListView, RecipeSearchView, MachineSearchView, PlainTextListView, LayerDetailView
from layerindex.views import LayerListView, RecipeSearchView, MachineSearchView, PlainTextListView, LayerDetailView, edit_layer_view
urlpatterns = patterns('',
url(r'^$',
LayerListView.as_view(
template_name='layerindex/index.html'),
name='layer_list'),
url(r'^submit/$', 'layerindex.views.submit_layer', name="submit_layer"),
url(r'^submit/$', edit_layer_view, {'template_name': 'layerindex/submitlayer.html'}, name="submit_layer"),
url(r'^edit/(?P<slug>[-\w]+)/$', edit_layer_view, {'template_name': 'layerindex/editlayer.html'}, name="edit_layer"),
url(r'^submit/thanks$', 'layerindex.views.submit_layer_thanks', name="submit_layer_thanks"),
url(r'^recipes/$',
RecipeSearchView.as_view(

View File

@ -23,15 +23,37 @@ import simplesearch
import settings
def submit_layer(request):
if request.method == 'POST':
def edit_layer_view(request, template_name, slug=None):
useredit = False
if slug:
# Edit mode
layeritem = get_object_or_404(LayerItem, name=slug)
if not (request.user.is_authenticated() and (request.user.has_perm('layerindex.publish_layer') or layeritem.user_can_edit(request.user))):
raise PermissionDenied
else:
# Submit mode
layeritem = LayerItem()
if request.method == 'POST':
form = SubmitLayerForm(request.POST, instance=layeritem)
maintainerformset = LayerMaintainerFormSet(request.POST, instance=layeritem)
if form.is_valid() and maintainerformset.is_valid():
with transaction.commit_on_success():
form.save()
maintainerformset.save()
if slug:
new_deps = form.cleaned_data['deps']
existing_deps = [deprec.dependency for deprec in layeritem.dependencies_set.all()]
for dep in new_deps:
if dep not in existing_deps:
deprec = LayerDependency()
deprec.layer = layeritem
deprec.dependency = dep
deprec.save()
for dep in existing_deps:
if dep not in new_deps:
layeritem.dependencies_set.filter(dependency=dep).delete()
else:
# Save dependencies
for dep in form.cleaned_data['deps']:
deprec = LayerDependency()
@ -55,14 +77,16 @@ def submit_layer(request):
msg = EmailMessage(subject, text_content, from_email, [to_email])
msg.send()
return HttpResponseRedirect(reverse('submit_layer_thanks'))
form.was_saved = True
else:
form = SubmitLayerForm()
maintainerformset = LayerMaintainerFormSet()
form = SubmitLayerForm(instance=layeritem)
maintainerformset = LayerMaintainerFormSet(instance=layeritem)
return render(request, 'layerindex/submitlayer.html', {
return render(request, template_name, {
'form': form,
'maintainerformset': maintainerformset,
'deplistlayers': LayerItem.objects.all().order_by('name')
'deplistlayers': LayerItem.objects.all().order_by('name'),
'useredit': useredit
})
def submit_layer_thanks(request):
@ -98,7 +122,9 @@ class LayerDetailView(DetailView):
model = LayerItem
slug_field = 'name'
# This is a bit of a mess. Surely there has to be a better way to handle this...
def dispatch(self, request, *args, **kwargs):
self.user = request.user
res = super(LayerDetailView, self).dispatch(request, *args, **kwargs)
l = self.get_object()
if l:
@ -107,6 +133,12 @@ class LayerDetailView(DetailView):
raise PermissionDenied
return res
def get_context_data(self, **kwargs):
context = super(LayerDetailView, self).get_context_data(**kwargs)
layer = context['layeritem']
context['useredit'] = layer.user_can_edit(self.user)
return context
class RecipeSearchView(ListView):
context_object_name = 'recipe_list'
paginate_by = 50