mirror of
git://git.yoctoproject.org/layerindex-web.git
synced 2025-07-04 20:54:47 +02:00
Add support for importing OE-Classic recipes
Add a script for doing a one-time import of OE-Classic recipe information, so comparisons against OE-Core can be performed; this is stored using a new ClassicRecipe model supporting additional fields for tracking migration status. The migration status fields can be updated as well as viewed and summarised in graph format. Signed-off-by: Paul Eggleton <paul.eggleton@linux.intel.com>
This commit is contained in:
parent
e8d734a377
commit
c3a8eb4d82
5
README
5
README
|
@ -24,6 +24,7 @@ In order to make use of this application you will need:
|
|||
* django-reversion (1.6.5)
|
||||
* django-reversion-compare (0.3.5)
|
||||
* django-simple-captcha (0.3.6)
|
||||
* django-nvd3 (0.1.11)
|
||||
* On the machine that will run the backend update script (which does not
|
||||
have to be the same machine as the web server, however it does still
|
||||
have to have Django installed, have the same or similar configuration
|
||||
|
@ -115,6 +116,10 @@ Bundled jQuery is redistributed under the MIT license.
|
|||
|
||||
Bundled uitablefilter.js is redistributed under the MIT license.
|
||||
|
||||
Bundled nv.d3.js is redistributed under the Apache License 2.0.
|
||||
|
||||
Bundled d3.v2.js is redistributed under the BSD License.
|
||||
|
||||
All other content is copyright (C) 2013 Intel Corporation and licensed
|
||||
under the MIT license (unless otherwise noted) - see COPYING.MIT for
|
||||
details.
|
||||
|
|
|
@ -45,6 +45,15 @@ class LayerNoteAdmin(CompareVersionAdmin):
|
|||
list_filter = ['layer__name']
|
||||
|
||||
class RecipeAdmin(admin.ModelAdmin):
|
||||
search_fields = ['filename', 'pn']
|
||||
list_filter = ['layerbranch__layer__name', 'layerbranch__branch__name']
|
||||
readonly_fields = [fieldname for fieldname in Recipe._meta.get_all_field_names() if fieldname not in ['recipefiledependency', 'classicrecipe']]
|
||||
def has_add_permission(self, request, obj=None):
|
||||
return False
|
||||
def has_delete_permission(self, request, obj=None):
|
||||
return False
|
||||
|
||||
class ClassicRecipeAdmin(admin.ModelAdmin):
|
||||
search_fields = ['filename', 'pn']
|
||||
list_filter = ['layerbranch__layer__name', 'layerbranch__branch__name']
|
||||
readonly_fields = [fieldname for fieldname in Recipe._meta.get_all_field_names() if fieldname != 'recipefiledependency']
|
||||
|
@ -101,3 +110,4 @@ admin.site.register(Machine, MachineAdmin)
|
|||
admin.site.register(BBAppend, BBAppendAdmin)
|
||||
admin.site.register(BBClass, BBClassAdmin)
|
||||
admin.site.register(RecipeChangeset, RecipeChangesetAdmin)
|
||||
admin.site.register(ClassicRecipe, ClassicRecipeAdmin)
|
||||
|
|
|
@ -11,7 +11,8 @@ def layerindex_context(request):
|
|||
if not current_branch:
|
||||
current_branch = 'master'
|
||||
return {
|
||||
'all_branches': Branch.objects.all().order_by('sort_priority'),
|
||||
'all_branches': Branch.objects.exclude(name='oe-classic').order_by('sort_priority'),
|
||||
'current_branch': current_branch,
|
||||
'unpublished_count': LayerItem.objects.filter(status='N').count(),
|
||||
'oe_classic': Branch.objects.filter(name='oe-classic')
|
||||
}
|
|
@ -4,7 +4,7 @@
|
|||
#
|
||||
# Licensed under the MIT license, see COPYING.MIT for details
|
||||
|
||||
from layerindex.models import LayerItem, LayerBranch, LayerMaintainer, LayerNote, RecipeChangeset, RecipeChange
|
||||
from layerindex.models import LayerItem, LayerBranch, LayerMaintainer, LayerNote, RecipeChangeset, RecipeChange, ClassicRecipe
|
||||
from django import forms
|
||||
from django.core.validators import URLValidator, RegexValidator, email_re
|
||||
from django.forms.models import inlineformset_factory, modelformset_factory
|
||||
|
@ -48,7 +48,7 @@ LayerMaintainerFormSet = inlineformset_factory(LayerBranch, LayerMaintainer, for
|
|||
class EditLayerForm(forms.ModelForm):
|
||||
# Additional form fields
|
||||
vcs_subdir = forms.CharField(label='Repository subdirectory', max_length=40, required=False, help_text='Subdirectory within the repository where the layer is located, if not in the root (usually only used if the repository contains more than one layer)')
|
||||
deps = forms.ModelMultipleChoiceField(label='Other layers this layer depends upon', queryset=LayerItem.objects.all(), required=False)
|
||||
deps = forms.ModelMultipleChoiceField(label='Other layers this layer depends upon', queryset=LayerItem.objects.filter(classic=False), required=False)
|
||||
captcha = CaptchaField(label='Verification', help_text='Please enter the letters displayed for verification purposes', error_messages={'invalid':'Incorrect entry, please try again'})
|
||||
|
||||
class Meta:
|
||||
|
@ -149,6 +149,12 @@ class EditProfileForm(forms.ModelForm):
|
|||
fields = ('first_name', 'last_name', 'email')
|
||||
|
||||
|
||||
class ClassicRecipeForm(forms.ModelForm):
|
||||
class Meta:
|
||||
model = ClassicRecipe
|
||||
fields = ('cover_layerbranch', 'cover_pn', 'cover_status', 'cover_verified', 'cover_comment', 'classic_category')
|
||||
|
||||
|
||||
class AdvancedRecipeSearchForm(forms.Form):
|
||||
FIELD_CHOICES = (
|
||||
('pn', 'Name'),
|
||||
|
@ -168,7 +174,7 @@ class AdvancedRecipeSearchForm(forms.Form):
|
|||
field = forms.ChoiceField(choices=FIELD_CHOICES)
|
||||
match_type = forms.ChoiceField(choices=MATCH_TYPE_CHOICES)
|
||||
value = forms.CharField(max_length=255, required=False)
|
||||
layer = forms.ModelChoiceField(queryset=LayerItem.objects.filter(status='P').order_by('name'), empty_label="(any)", required=False)
|
||||
layer = forms.ModelChoiceField(queryset=LayerItem.objects.filter(classic=False).filter(status='P').order_by('name'), empty_label="(any)", required=False)
|
||||
|
||||
|
||||
class RecipeChangesetForm(forms.ModelForm):
|
||||
|
@ -202,3 +208,18 @@ class BulkChangeEditForm(forms.ModelForm):
|
|||
setattr(self.instance, fieldname, '')
|
||||
|
||||
BulkChangeEditFormSet = modelformset_factory(RecipeChange, form=BulkChangeEditForm, extra=0)
|
||||
|
||||
|
||||
class ClassicRecipeSearchForm(forms.Form):
|
||||
COVER_STATUS_CHOICES = [('','(any)'), ('!','(not migrated)')] + ClassicRecipe.COVER_STATUS_CHOICES
|
||||
VERIFIED_CHOICES = [
|
||||
('', '(any)'),
|
||||
('1', 'Verified'),
|
||||
('0', 'Unverified'),
|
||||
]
|
||||
|
||||
q = forms.CharField(label='Keyword', max_length=255, required=False)
|
||||
category = forms.CharField(max_length=255, required=False)
|
||||
cover_status = forms.ChoiceField(label='Status', choices=COVER_STATUS_CHOICES, required=False)
|
||||
cover_verified = forms.ChoiceField(label='Verified', choices=VERIFIED_CHOICES, required=False)
|
||||
|
||||
|
|
205
layerindex/migrations/0007_auto__add_classicrecipe.py
Normal file
205
layerindex/migrations/0007_auto__add_classicrecipe.py
Normal file
|
@ -0,0 +1,205 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
import datetime
|
||||
from south.db import db
|
||||
from south.v2 import SchemaMigration
|
||||
from django.db import models
|
||||
|
||||
|
||||
class Migration(SchemaMigration):
|
||||
|
||||
def forwards(self, orm):
|
||||
# Adding model 'ClassicRecipe'
|
||||
db.create_table('layerindex_classicrecipe', (
|
||||
('recipe_ptr', self.gf('django.db.models.fields.related.OneToOneField')(to=orm['layerindex.Recipe'], unique=True, primary_key=True)),
|
||||
('cover_layerbranch', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['layerindex.LayerBranch'], null=True, on_delete=models.SET_NULL, blank=True)),
|
||||
('cover_pn', self.gf('django.db.models.fields.CharField')(max_length=100, blank=True)),
|
||||
('cover_status', self.gf('django.db.models.fields.CharField')(default='U', max_length=1)),
|
||||
('cover_verified', self.gf('django.db.models.fields.BooleanField')(default=False)),
|
||||
('cover_comment', self.gf('django.db.models.fields.TextField')(blank=True)),
|
||||
('classic_category', self.gf('django.db.models.fields.CharField')(max_length=100, blank=True)),
|
||||
))
|
||||
db.send_create_signal('layerindex', ['ClassicRecipe'])
|
||||
|
||||
# Adding field 'LayerItem.classic'
|
||||
db.add_column('layerindex_layeritem', 'classic',
|
||||
self.gf('django.db.models.fields.BooleanField')(default=False),
|
||||
keep_default=False)
|
||||
|
||||
|
||||
def backwards(self, orm):
|
||||
# Deleting model 'ClassicRecipe'
|
||||
db.delete_table('layerindex_classicrecipe')
|
||||
|
||||
# Deleting field 'LayerItem.classic'
|
||||
db.delete_column('layerindex_layeritem', 'classic')
|
||||
|
||||
|
||||
models = {
|
||||
'auth.group': {
|
||||
'Meta': {'object_name': 'Group'},
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
|
||||
'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
|
||||
},
|
||||
'auth.permission': {
|
||||
'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'},
|
||||
'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
|
||||
'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
|
||||
},
|
||||
'auth.user': {
|
||||
'Meta': {'object_name': 'User'},
|
||||
'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
|
||||
'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
|
||||
'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
|
||||
'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
|
||||
'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
|
||||
'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
|
||||
'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
|
||||
'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
|
||||
'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
|
||||
},
|
||||
'contenttypes.contenttype': {
|
||||
'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
|
||||
'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
|
||||
},
|
||||
'layerindex.bbappend': {
|
||||
'Meta': {'object_name': 'BBAppend'},
|
||||
'filename': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
|
||||
'filepath': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'layerbranch': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['layerindex.LayerBranch']"})
|
||||
},
|
||||
'layerindex.bbclass': {
|
||||
'Meta': {'object_name': 'BBClass'},
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'layerbranch': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['layerindex.LayerBranch']"}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
|
||||
},
|
||||
'layerindex.branch': {
|
||||
'Meta': {'object_name': 'Branch'},
|
||||
'bitbake_branch': ('django.db.models.fields.CharField', [], {'max_length': '50'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}),
|
||||
'short_description': ('django.db.models.fields.CharField', [], {'max_length': '50', 'blank': 'True'}),
|
||||
'sort_priority': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'})
|
||||
},
|
||||
'layerindex.classicrecipe': {
|
||||
'Meta': {'object_name': 'ClassicRecipe', '_ormbases': ['layerindex.Recipe']},
|
||||
'classic_category': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}),
|
||||
'cover_comment': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
|
||||
'cover_layerbranch': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['layerindex.LayerBranch']", 'null': 'True', 'on_delete': 'models.SET_NULL', 'blank': 'True'}),
|
||||
'cover_pn': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}),
|
||||
'cover_status': ('django.db.models.fields.CharField', [], {'default': "'U'", 'max_length': '1'}),
|
||||
'cover_verified': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'recipe_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['layerindex.Recipe']", 'unique': 'True', 'primary_key': 'True'})
|
||||
},
|
||||
'layerindex.layerbranch': {
|
||||
'Meta': {'object_name': 'LayerBranch'},
|
||||
'actual_branch': ('django.db.models.fields.CharField', [], {'max_length': '80', 'blank': 'True'}),
|
||||
'branch': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['layerindex.Branch']"}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'layer': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['layerindex.LayerItem']"}),
|
||||
'vcs_last_commit': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
|
||||
'vcs_last_fetch': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
|
||||
'vcs_last_rev': ('django.db.models.fields.CharField', [], {'max_length': '80', 'blank': 'True'}),
|
||||
'vcs_subdir': ('django.db.models.fields.CharField', [], {'max_length': '40', 'blank': 'True'})
|
||||
},
|
||||
'layerindex.layerdependency': {
|
||||
'Meta': {'object_name': 'LayerDependency'},
|
||||
'dependency': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'dependents_set'", 'to': "orm['layerindex.LayerItem']"}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'layerbranch': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'dependencies_set'", 'to': "orm['layerindex.LayerBranch']"})
|
||||
},
|
||||
'layerindex.layeritem': {
|
||||
'Meta': {'object_name': 'LayerItem'},
|
||||
'classic': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'description': ('django.db.models.fields.TextField', [], {}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'index_preference': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
|
||||
'layer_type': ('django.db.models.fields.CharField', [], {'max_length': '1'}),
|
||||
'mailing_list_url': ('django.db.models.fields.URLField', [], {'max_length': '200', 'blank': 'True'}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '40'}),
|
||||
'status': ('django.db.models.fields.CharField', [], {'default': "'N'", 'max_length': '1'}),
|
||||
'summary': ('django.db.models.fields.CharField', [], {'max_length': '200'}),
|
||||
'usage_url': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
|
||||
'vcs_url': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
|
||||
'vcs_web_file_base_url': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
|
||||
'vcs_web_tree_base_url': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
|
||||
'vcs_web_url': ('django.db.models.fields.URLField', [], {'max_length': '200', 'blank': 'True'})
|
||||
},
|
||||
'layerindex.layermaintainer': {
|
||||
'Meta': {'object_name': 'LayerMaintainer'},
|
||||
'email': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'layerbranch': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['layerindex.LayerBranch']"}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
|
||||
'responsibility': ('django.db.models.fields.CharField', [], {'max_length': '200', 'blank': 'True'}),
|
||||
'status': ('django.db.models.fields.CharField', [], {'default': "'A'", 'max_length': '1'})
|
||||
},
|
||||
'layerindex.layernote': {
|
||||
'Meta': {'object_name': 'LayerNote'},
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'layer': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['layerindex.LayerItem']"}),
|
||||
'text': ('django.db.models.fields.TextField', [], {})
|
||||
},
|
||||
'layerindex.machine': {
|
||||
'Meta': {'object_name': 'Machine'},
|
||||
'description': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'layerbranch': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['layerindex.LayerBranch']"}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'})
|
||||
},
|
||||
'layerindex.recipe': {
|
||||
'Meta': {'object_name': 'Recipe'},
|
||||
'bbclassextend': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}),
|
||||
'bugtracker': ('django.db.models.fields.URLField', [], {'max_length': '200', 'blank': 'True'}),
|
||||
'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
|
||||
'filename': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
|
||||
'filepath': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
|
||||
'homepage': ('django.db.models.fields.URLField', [], {'max_length': '200', 'blank': 'True'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'layerbranch': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['layerindex.LayerBranch']"}),
|
||||
'license': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}),
|
||||
'pn': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}),
|
||||
'provides': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
|
||||
'pv': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}),
|
||||
'section': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}),
|
||||
'summary': ('django.db.models.fields.CharField', [], {'max_length': '200', 'blank': 'True'})
|
||||
},
|
||||
'layerindex.recipechange': {
|
||||
'Meta': {'object_name': 'RecipeChange'},
|
||||
'bugtracker': ('django.db.models.fields.URLField', [], {'max_length': '200', 'blank': 'True'}),
|
||||
'changeset': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['layerindex.RecipeChangeset']"}),
|
||||
'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
|
||||
'homepage': ('django.db.models.fields.URLField', [], {'max_length': '200', 'blank': 'True'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'license': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}),
|
||||
'recipe': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'+'", 'to': "orm['layerindex.Recipe']"}),
|
||||
'section': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}),
|
||||
'summary': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'})
|
||||
},
|
||||
'layerindex.recipechangeset': {
|
||||
'Meta': {'object_name': 'RecipeChangeset'},
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
|
||||
'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"})
|
||||
},
|
||||
'layerindex.recipefiledependency': {
|
||||
'Meta': {'object_name': 'RecipeFileDependency'},
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'layerbranch': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'+'", 'to': "orm['layerindex.LayerBranch']"}),
|
||||
'path': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}),
|
||||
'recipe': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['layerindex.Recipe']"})
|
||||
}
|
||||
}
|
||||
|
||||
complete_apps = ['layerindex']
|
|
@ -51,6 +51,7 @@ class LayerItem(models.Model):
|
|||
usage_url = models.CharField('Usage web page URL', max_length=255, blank=True, help_text='URL of a web page with more information about the layer and how to use it, if any (or path to file within repository)')
|
||||
mailing_list_url = models.URLField('Mailing list URL', blank=True, help_text='URL of the info page for a mailing list for discussing the layer, if any')
|
||||
index_preference = models.IntegerField('Preference', default=0, help_text='Number used to find preferred recipes in recipe search results (higher number is greater preference)')
|
||||
classic = models.BooleanField('Classic', default=False, help_text='Is this OE-Classic?')
|
||||
|
||||
class Meta:
|
||||
verbose_name = "Layer"
|
||||
|
@ -260,6 +261,54 @@ class RecipeFileDependency(models.Model):
|
|||
return '%s' % self.path
|
||||
|
||||
|
||||
class ClassicRecipe(Recipe):
|
||||
COVER_STATUS_CHOICES = [
|
||||
('U', 'Unknown'),
|
||||
('N', 'Not available'),
|
||||
('R', 'Replaced'),
|
||||
('P', 'Provided (BBCLASSEXTEND)'),
|
||||
('C', 'Provided (PACKAGECONFIG)'),
|
||||
('O', 'Obsolete'),
|
||||
('E', 'Equivalent functionality'),
|
||||
('D', 'Direct match'),
|
||||
]
|
||||
cover_layerbranch = models.ForeignKey(LayerBranch, verbose_name='Covering layer', blank=True, null=True, limit_choices_to = {'branch__name': 'master'}, on_delete=models.SET_NULL)
|
||||
cover_pn = models.CharField('Covering recipe', max_length=100, blank=True)
|
||||
cover_status = models.CharField(max_length=1, choices=COVER_STATUS_CHOICES, default='U')
|
||||
cover_verified = models.BooleanField(default=False)
|
||||
cover_comment = models.TextField(blank=True)
|
||||
classic_category = models.CharField('OE-Classic Category', max_length=100, blank=True)
|
||||
|
||||
class Meta:
|
||||
permissions = (
|
||||
("edit_classic", "Can edit OE-Classic recipes"),
|
||||
)
|
||||
|
||||
def get_cover_desc(self):
|
||||
desc = self.get_cover_status_display()
|
||||
if self.cover_layerbranch:
|
||||
cover_layer = self.cover_layerbranch.layer.name
|
||||
else:
|
||||
cover_layer = '(unknown layer)'
|
||||
if self.cover_status == 'D':
|
||||
desc = 'Direct match exists in %s' % cover_layer
|
||||
elif self.cover_pn:
|
||||
if self.cover_status == 'R':
|
||||
desc = 'Replaced by %s in %s' % (self.cover_pn, cover_layer)
|
||||
elif self.cover_status == 'P':
|
||||
desc = 'Provided by %s in %s (BBCLASSEXTEND)' % (self.cover_pn, cover_layer)
|
||||
elif self.cover_status == 'C':
|
||||
desc = 'Provided by %s in %s (as a PACKAGECONFIG option)' % (self.cover_pn, cover_layer)
|
||||
elif self.cover_status == 'E':
|
||||
desc = 'Equivalent functionality provided by %s in %s' % (self.cover_pn, cover_layer)
|
||||
if self.cover_comment:
|
||||
if self.cover_comment[0] == '(':
|
||||
desc = "%s %s" % (desc, self.cover_comment)
|
||||
else:
|
||||
desc = "%s - %s" % (desc, self.cover_comment)
|
||||
return desc
|
||||
|
||||
|
||||
class Machine(models.Model):
|
||||
layerbranch = models.ForeignKey(LayerBranch)
|
||||
name = models.CharField(max_length=255)
|
||||
|
|
96
layerindex/recipedesc.py
Normal file
96
layerindex/recipedesc.py
Normal file
|
@ -0,0 +1,96 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
# Test script
|
||||
#
|
||||
# Copyright (C) 2012 Intel Corporation
|
||||
# Author: Paul Eggleton <paul.eggleton@linux.intel.com>
|
||||
|
||||
import sys
|
||||
import os.path
|
||||
import logging
|
||||
import subprocess
|
||||
from datetime import datetime
|
||||
import fnmatch
|
||||
from distutils.version import LooseVersion
|
||||
|
||||
|
||||
logger = None
|
||||
|
||||
|
||||
def sanitise_path(inpath):
|
||||
outpath = ""
|
||||
for c in inpath:
|
||||
if c in '/ .=+?:':
|
||||
outpath += "_"
|
||||
else:
|
||||
outpath += c
|
||||
return outpath
|
||||
|
||||
def main():
|
||||
# Get access to our Django model
|
||||
newpath = os.path.abspath(os.path.dirname(os.path.abspath(sys.argv[0])) + '/..')
|
||||
sys.path.append(newpath)
|
||||
os.environ['DJANGO_SETTINGS_MODULE'] = 'settings'
|
||||
|
||||
from django.core.management import setup_environ
|
||||
from django.conf import settings
|
||||
from layerindex.models import LayerItem, Recipe
|
||||
from django.db import transaction
|
||||
import settings
|
||||
|
||||
setup_environ(settings)
|
||||
|
||||
# Set path to bitbake lib dir
|
||||
basepath = os.path.abspath(sys.argv[1])
|
||||
bitbakedir_env = os.environ.get('BITBAKEDIR', '')
|
||||
if bitbakedir_env and os.path.exists(bitbakedir_env + '/lib/bb'):
|
||||
bitbakepath = bitbakedir_env
|
||||
elif os.path.exists(basepath + '/bitbake/lib/bb'):
|
||||
bitbakepath = basepath + '/bitbake'
|
||||
elif os.path.exists(basepath + '/../bitbake/lib/bb'):
|
||||
bitbakepath = os.path.abspath(basepath + '/../bitbake')
|
||||
else:
|
||||
# look for bitbake/bin dir in PATH
|
||||
bitbakepath = None
|
||||
for pth in os.environ['PATH'].split(':'):
|
||||
if os.path.exists(os.path.join(pth, '../lib/bb')):
|
||||
bitbakepath = os.path.abspath(os.path.join(pth, '..'))
|
||||
break
|
||||
if not bitbakepath:
|
||||
print("Unable to find bitbake by searching BITBAKEDIR, specified path '%s' or its parent, or PATH" % basepath)
|
||||
sys.exit(1)
|
||||
|
||||
# Skip sanity checks
|
||||
os.environ['BB_ENV_EXTRAWHITE'] = 'DISABLE_SANITY_CHECKS'
|
||||
os.environ['DISABLE_SANITY_CHECKS'] = '1'
|
||||
|
||||
sys.path.extend([bitbakepath + '/lib'])
|
||||
import bb.tinfoil
|
||||
tinfoil = bb.tinfoil.Tinfoil()
|
||||
tinfoil.prepare(config_only = True)
|
||||
|
||||
logger = logging.getLogger('BitBake')
|
||||
|
||||
fetchdir = settings.LAYER_FETCH_DIR
|
||||
if not fetchdir:
|
||||
logger.error("Please set LAYER_FETCH_DIR in settings.py")
|
||||
sys.exit(1)
|
||||
|
||||
for layer in LayerItem.objects.filter(status='P'):
|
||||
urldir = sanitise_path(layer.vcs_url)
|
||||
repodir = os.path.join(fetchdir, urldir)
|
||||
layerrecipes = Recipe.objects.filter(layer=layer)
|
||||
for recipe in layerrecipes:
|
||||
fullpath = str(os.path.join(repodir, layer.vcs_subdir, recipe.filepath, recipe.filename))
|
||||
print fullpath
|
||||
try:
|
||||
envdata = bb.cache.Cache.loadDataFull(fullpath, [], tinfoil.config_data)
|
||||
print "DESCRIPTION = \"%s\"" % envdata.getVar("DESCRIPTION", True)
|
||||
except Exception as e:
|
||||
logger.info("Unable to read %s: %s", fullpath, str(e))
|
||||
|
||||
sys.exit(0)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
|
@ -10,6 +10,8 @@ import os
|
|||
import os.path
|
||||
import utils
|
||||
import tempfile
|
||||
import re
|
||||
import fnmatch
|
||||
|
||||
class RecipeParseError(Exception):
|
||||
def __init__(self, msg):
|
||||
|
@ -41,8 +43,8 @@ def _parse_layer_conf(layerdir, data):
|
|||
data.expandVarref('LAYERDIR')
|
||||
|
||||
|
||||
def init_parser(settings, branch, bitbakepath, enable_tracking=False, nocheckout=False):
|
||||
if not nocheckout:
|
||||
def init_parser(settings, branch, bitbakepath, enable_tracking=False, nocheckout=False, classic=False):
|
||||
if not (nocheckout or classic):
|
||||
# Check out the branch of BitBake appropriate for this branch and clean out any stale files (e.g. *.pyc)
|
||||
out = utils.runcmd("git checkout origin/%s" % branch.bitbake_branch, bitbakepath)
|
||||
out = utils.runcmd("git clean -f -x", bitbakepath)
|
||||
|
@ -53,28 +55,29 @@ def init_parser(settings, branch, bitbakepath, enable_tracking=False, nocheckout
|
|||
|
||||
fetchdir = settings.LAYER_FETCH_DIR
|
||||
|
||||
# Ensure we have OE-Core set up to get some base configuration
|
||||
core_layer = utils.get_layer(settings.CORE_LAYER_NAME)
|
||||
if not core_layer:
|
||||
raise RecipeParseError("Unable to find core layer %s in database; check CORE_LAYER_NAME setting" % settings.CORE_LAYER_NAME)
|
||||
core_layerbranch = core_layer.get_layerbranch(branch.name)
|
||||
core_branchname = branch.name
|
||||
if core_layerbranch:
|
||||
core_subdir = core_layerbranch.vcs_subdir
|
||||
if core_layerbranch.actual_branch:
|
||||
core_branchname = core_layerbranch.actual_branch
|
||||
else:
|
||||
core_subdir = 'meta'
|
||||
core_urldir = core_layer.get_fetch_dir()
|
||||
core_repodir = os.path.join(fetchdir, core_urldir)
|
||||
core_layerdir = os.path.join(core_repodir, core_subdir)
|
||||
if not nocheckout:
|
||||
out = utils.runcmd("git checkout origin/%s" % core_branchname, core_repodir)
|
||||
out = utils.runcmd("git clean -f -x", core_repodir)
|
||||
# The directory above where this script exists should contain our conf/layer.conf,
|
||||
# so add it to BBPATH along with the core layer directory
|
||||
confparentdir = os.path.abspath(os.path.join(os.path.dirname(os.path.abspath(__file__)), '..'))
|
||||
os.environ['BBPATH'] = str("%s:%s" % (confparentdir, core_layerdir))
|
||||
if not classic:
|
||||
# Ensure we have OE-Core set up to get some base configuration
|
||||
core_layer = utils.get_layer(settings.CORE_LAYER_NAME)
|
||||
if not core_layer:
|
||||
raise RecipeParseError("Unable to find core layer %s in database; check CORE_LAYER_NAME setting" % settings.CORE_LAYER_NAME)
|
||||
core_layerbranch = core_layer.get_layerbranch(branch.name)
|
||||
core_branchname = branch.name
|
||||
if core_layerbranch:
|
||||
core_subdir = core_layerbranch.vcs_subdir
|
||||
if core_layerbranch.actual_branch:
|
||||
core_branchname = core_layerbranch.actual_branch
|
||||
else:
|
||||
core_subdir = 'meta'
|
||||
core_urldir = core_layer.get_fetch_dir()
|
||||
core_repodir = os.path.join(fetchdir, core_urldir)
|
||||
core_layerdir = os.path.join(core_repodir, core_subdir)
|
||||
if not nocheckout:
|
||||
out = utils.runcmd("git checkout origin/%s" % core_branchname, core_repodir)
|
||||
out = utils.runcmd("git clean -f -x", core_repodir)
|
||||
# The directory above where this script exists should contain our conf/layer.conf,
|
||||
# so add it to BBPATH along with the core layer directory
|
||||
confparentdir = os.path.abspath(os.path.join(os.path.dirname(os.path.abspath(__file__)), '..'))
|
||||
os.environ['BBPATH'] = str("%s:%s" % (confparentdir, core_layerdir))
|
||||
|
||||
# Change into a temporary directory so we don't write the cache and other files to the current dir
|
||||
if not os.path.exists(settings.TEMP_BASE_DIR):
|
||||
|
@ -86,7 +89,8 @@ def init_parser(settings, branch, bitbakepath, enable_tracking=False, nocheckout
|
|||
|
||||
# Ensure TMPDIR exists (or insane.bbclass will blow up trying to write to the QA log)
|
||||
oe_tmpdir = tinfoil.config_data.getVar('TMPDIR', True)
|
||||
os.makedirs(oe_tmpdir)
|
||||
if not os.path.exists(oe_tmpdir):
|
||||
os.makedirs(oe_tmpdir)
|
||||
|
||||
return (tinfoil, tempdir)
|
||||
|
||||
|
@ -130,3 +134,34 @@ def get_var_files(fn, varlist, d):
|
|||
varfiles[v] = actualfile
|
||||
|
||||
return varfiles
|
||||
|
||||
machine_conf_re = re.compile(r'conf/machine/([^/.]*).conf$')
|
||||
bbclass_re = re.compile(r'classes/([^/.]*).bbclass$')
|
||||
def detect_file_type(path, subdir_start):
|
||||
typename = None
|
||||
if fnmatch.fnmatch(path, "*.bb"):
|
||||
typename = 'recipe'
|
||||
elif fnmatch.fnmatch(path, "*.bbappend"):
|
||||
typename = 'bbappend'
|
||||
else:
|
||||
# Check if it's a machine conf file
|
||||
subpath = path[len(subdir_start):]
|
||||
res = machine_conf_re.match(subpath)
|
||||
if res:
|
||||
typename = 'machine'
|
||||
return (typename, None, res.group(1))
|
||||
else:
|
||||
res = bbclass_re.match(subpath)
|
||||
if res:
|
||||
typename = 'bbclass'
|
||||
return (typename, None, res.group(1))
|
||||
|
||||
if typename == 'recipe' or typename == 'bbappend':
|
||||
if subdir_start:
|
||||
filepath = os.path.relpath(os.path.dirname(path), subdir_start)
|
||||
else:
|
||||
filepath = os.path.dirname(path)
|
||||
return (typename, filepath, os.path.basename(path))
|
||||
|
||||
return (None, None, None)
|
||||
|
||||
|
|
645
layerindex/static/css/nv.d3.css
Normal file
645
layerindex/static/css/nv.d3.css
Normal file
|
@ -0,0 +1,645 @@
|
|||
|
||||
/********************
|
||||
* HTML CSS
|
||||
*/
|
||||
|
||||
|
||||
.chartWrap {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
|
||||
/********************
|
||||
* TOOLTIP CSS
|
||||
*/
|
||||
|
||||
.nvtooltip {
|
||||
position: absolute;
|
||||
background-color: rgba(255,255,255,1);
|
||||
padding: 10px;
|
||||
border: 1px solid #ddd;
|
||||
z-index: 10000;
|
||||
|
||||
font-family: Arial;
|
||||
font-size: 13px;
|
||||
|
||||
transition: opacity 500ms linear;
|
||||
-moz-transition: opacity 500ms linear;
|
||||
-webkit-transition: opacity 500ms linear;
|
||||
|
||||
transition-delay: 500ms;
|
||||
-moz-transition-delay: 500ms;
|
||||
-webkit-transition-delay: 500ms;
|
||||
|
||||
-moz-box-shadow: 4px 4px 8px rgba(0,0,0,.5);
|
||||
-webkit-box-shadow: 4px 4px 8px rgba(0,0,0,.5);
|
||||
box-shadow: 4px 4px 8px rgba(0,0,0,.5);
|
||||
|
||||
-moz-border-radius: 10px;
|
||||
border-radius: 10px;
|
||||
|
||||
pointer-events: none;
|
||||
|
||||
-webkit-touch-callout: none;
|
||||
-webkit-user-select: none;
|
||||
-khtml-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.nvtooltip h3 {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.nvtooltip p {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.nvtooltip span {
|
||||
display: inline-block;
|
||||
margin: 2px 0;
|
||||
}
|
||||
|
||||
.nvtooltip-pending-removal {
|
||||
position: absolute;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
|
||||
/********************
|
||||
* SVG CSS
|
||||
*/
|
||||
|
||||
|
||||
svg {
|
||||
-webkit-touch-callout: none;
|
||||
-webkit-user-select: none;
|
||||
-khtml-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
/* Trying to get SVG to act like a greedy block in all browsers */
|
||||
display: block;
|
||||
width:100%;
|
||||
height:100%;
|
||||
}
|
||||
|
||||
|
||||
svg text {
|
||||
font: normal 12px sans-serif;
|
||||
}
|
||||
|
||||
svg .title {
|
||||
font: bold 14px Arial;
|
||||
}
|
||||
|
||||
.nvd3 .nv-background {
|
||||
fill: white;
|
||||
fill-opacity: 0;
|
||||
/*
|
||||
pointer-events: none;
|
||||
*/
|
||||
}
|
||||
|
||||
.nvd3.nv-noData {
|
||||
font-size: 18px;
|
||||
font-weight: bolf;
|
||||
}
|
||||
|
||||
|
||||
/**********
|
||||
* Brush
|
||||
*/
|
||||
|
||||
.nv-brush .extent {
|
||||
fill-opacity: .125;
|
||||
shape-rendering: crispEdges;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**********
|
||||
* Legend
|
||||
*/
|
||||
|
||||
.nvd3 .nv-legend .nv-series {
|
||||
}
|
||||
|
||||
.nvd3 .nv-legend .disabled circle {
|
||||
fill-opacity: 0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**********
|
||||
* Axes
|
||||
*/
|
||||
|
||||
.nvd3 .nv-axis path {
|
||||
fill: none;
|
||||
stroke: #000;
|
||||
stroke-opacity: .75;
|
||||
shape-rendering: crispEdges;
|
||||
}
|
||||
|
||||
.nvd3 .nv-axis path.domain {
|
||||
stroke-opacity: .75;
|
||||
}
|
||||
|
||||
.nvd3 .nv-axis.nv-x path.domain {
|
||||
stroke-opacity: 0;
|
||||
}
|
||||
|
||||
.nvd3 .nv-axis line {
|
||||
fill: none;
|
||||
stroke: #000;
|
||||
stroke-opacity: .25;
|
||||
shape-rendering: crispEdges;
|
||||
}
|
||||
|
||||
.nvd3 .nv-axis line.zero {
|
||||
stroke-opacity: .75;
|
||||
}
|
||||
|
||||
.nvd3 .nv-axis .nv-axisMaxMin text {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.nvd3 .x .nv-axis .nv-axisMaxMin text,
|
||||
.nvd3 .x2 .nv-axis .nv-axisMaxMin text,
|
||||
.nvd3 .x3 .nv-axis .nv-axisMaxMin text {
|
||||
text-anchor: middle
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**********
|
||||
* Brush
|
||||
*/
|
||||
|
||||
.nv-brush .resize path {
|
||||
fill: #eee;
|
||||
stroke: #666;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**********
|
||||
* Bars
|
||||
*/
|
||||
|
||||
.nvd3 .nv-bars .negative rect {
|
||||
zfill: brown;
|
||||
}
|
||||
|
||||
.nvd3 .nv-bars rect {
|
||||
zfill: steelblue;
|
||||
fill-opacity: .75;
|
||||
|
||||
transition: fill-opacity 250ms linear;
|
||||
-moz-transition: fill-opacity 250ms linear;
|
||||
-webkit-transition: fill-opacity 250ms linear;
|
||||
}
|
||||
|
||||
.nvd3 .nv-bars rect:hover {
|
||||
fill-opacity: 1;
|
||||
}
|
||||
|
||||
.nvd3 .nv-bars .hover rect {
|
||||
fill: lightblue;
|
||||
}
|
||||
|
||||
.nvd3 .nv-bars text {
|
||||
fill: rgba(0,0,0,0);
|
||||
}
|
||||
|
||||
.nvd3 .nv-bars .hover text {
|
||||
fill: rgba(0,0,0,1);
|
||||
}
|
||||
|
||||
.nvd3 .nv-x.nv-axis text {
|
||||
transform: rotate(90);
|
||||
}
|
||||
|
||||
|
||||
/**********
|
||||
* Bars
|
||||
*/
|
||||
|
||||
.nvd3 .nv-multibar .nv-groups rect,
|
||||
.nvd3 .nv-multibarHorizontal .nv-groups rect,
|
||||
.nvd3 .nv-discretebar .nv-groups rect {
|
||||
stroke-opacity: 0;
|
||||
|
||||
transition: fill-opacity 250ms linear;
|
||||
-moz-transition: fill-opacity 250ms linear;
|
||||
-webkit-transition: fill-opacity 250ms linear;
|
||||
}
|
||||
|
||||
.nvd3 .nv-multibar .nv-groups rect:hover,
|
||||
.nvd3 .nv-multibarHorizontal .nv-groups rect:hover,
|
||||
.nvd3 .nv-discretebar .nv-groups rect:hover {
|
||||
fill-opacity: 1;
|
||||
}
|
||||
|
||||
.nvd3 .nv-discretebar .nv-groups text,
|
||||
.nvd3 .nv-multibarHorizontal .nv-groups text {
|
||||
font-weight: bold;
|
||||
fill: rgba(0,0,0,1);
|
||||
stroke: rgba(0,0,0,0);
|
||||
}
|
||||
|
||||
/***********
|
||||
* Pie Chart
|
||||
*/
|
||||
|
||||
.nvd3.nv-pie path {
|
||||
stroke-opacity: 0;
|
||||
|
||||
transition: fill-opacity 250ms linear, stroke-width 250ms linear, stroke-opacity 250ms linear;
|
||||
-moz-transition: fill-opacity 250ms linear, stroke-width 250ms linear, stroke-opacity 250ms linear;
|
||||
-webkit-transition: fill-opacity 250ms linear, stroke-width 250ms linear, stroke-opacity 250ms linear;
|
||||
|
||||
}
|
||||
|
||||
.nvd3.nv-pie .nv-slice text {
|
||||
stroke: #000;
|
||||
stroke-width: 0;
|
||||
}
|
||||
|
||||
.nvd3.nv-pie path {
|
||||
stroke: #fff;
|
||||
stroke-width: 1px;
|
||||
stroke-opacity: 1;
|
||||
}
|
||||
|
||||
.nvd3.nv-pie .hover path {
|
||||
fill-opacity: .7;
|
||||
/*
|
||||
stroke-width: 6px;
|
||||
stroke-opacity: 1;
|
||||
*/
|
||||
}
|
||||
|
||||
.nvd3.nv-pie .nv-label rect {
|
||||
fill-opacity: 0;
|
||||
stroke-opacity: 0;
|
||||
}
|
||||
|
||||
/**********
|
||||
* Lines
|
||||
*/
|
||||
|
||||
.nvd3 .nv-groups path.nv-line {
|
||||
fill: none;
|
||||
stroke-width: 2.5px;
|
||||
stroke-linecap: round;
|
||||
shape-rendering: geometricPrecision;
|
||||
|
||||
/*
|
||||
transition: stroke-width 250ms linear;
|
||||
-moz-transition: stroke-width 250ms linear;
|
||||
-webkit-transition: stroke-width 250ms linear;
|
||||
|
||||
transition-delay: 250ms
|
||||
-moz-transition-delay: 250ms;
|
||||
-webkit-transition-delay: 250ms;
|
||||
*/
|
||||
}
|
||||
|
||||
.nvd3 .nv-groups path.nv-area {
|
||||
stroke: none;
|
||||
stroke-linecap: round;
|
||||
shape-rendering: geometricPrecision;
|
||||
|
||||
/*
|
||||
stroke-width: 2.5px;
|
||||
transition: stroke-width 250ms linear;
|
||||
-moz-transition: stroke-width 250ms linear;
|
||||
-webkit-transition: stroke-width 250ms linear;
|
||||
|
||||
transition-delay: 250ms
|
||||
-moz-transition-delay: 250ms;
|
||||
-webkit-transition-delay: 250ms;
|
||||
*/
|
||||
}
|
||||
|
||||
.nvd3 .nv-line.hover path {
|
||||
stroke-width: 6px;
|
||||
}
|
||||
|
||||
/*
|
||||
.nvd3.scatter .groups .point {
|
||||
fill-opacity: 0.1;
|
||||
stroke-opacity: 0.1;
|
||||
}
|
||||
*/
|
||||
|
||||
.nvd3.nv-line .nvd3.nv-scatter .nv-groups .nv-point {
|
||||
fill-opacity: 0;
|
||||
stroke-opacity: 0;
|
||||
}
|
||||
|
||||
.nvd3.nv-scatter.nv-single-point .nv-groups .nv-point {
|
||||
fill-opacity: .5 !important;
|
||||
stroke-opacity: .5 !important;
|
||||
}
|
||||
|
||||
|
||||
.nvd3 .nv-groups .nv-point {
|
||||
transition: stroke-width 250ms linear, stroke-opacity 250ms linear;
|
||||
-moz-transition: stroke-width 250ms linear, stroke-opacity 250ms linear;
|
||||
-webkit-transition: stroke-width 250ms linear, stroke-opacity 250ms linear;
|
||||
}
|
||||
|
||||
.nvd3.nv-scatter .nv-groups .nv-point.hover,
|
||||
.nvd3 .nv-groups .nv-point.hover {
|
||||
stroke-width: 20px;
|
||||
fill-opacity: .5 !important;
|
||||
stroke-opacity: .5 !important;
|
||||
}
|
||||
|
||||
|
||||
.nvd3 .nv-point-paths path {
|
||||
stroke: #aaa;
|
||||
stroke-opacity: 0;
|
||||
fill: #eee;
|
||||
fill-opacity: 0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
.nvd3 .nv-indexLine {
|
||||
cursor: ew-resize;
|
||||
}
|
||||
|
||||
|
||||
/**********
|
||||
* Distribution
|
||||
*/
|
||||
|
||||
.nvd3 .nv-distribution {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**********
|
||||
* Scatter
|
||||
*/
|
||||
|
||||
.nvd3 .nv-groups .nv-point {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.nvd3 .nv-groups .nv-point.hover {
|
||||
stroke-width: 20px;
|
||||
stroke-opacity: .5;
|
||||
}
|
||||
|
||||
.nvd3 .nv-scatter .nv-point.hover {
|
||||
fill-opacity: 1;
|
||||
}
|
||||
|
||||
/*
|
||||
.nv-group.hover .nv-point {
|
||||
fill-opacity: 1;
|
||||
}
|
||||
*/
|
||||
|
||||
|
||||
/**********
|
||||
* Stacked Area
|
||||
*/
|
||||
|
||||
.nvd3.nv-stackedarea path.nv-area {
|
||||
fill-opacity: .7;
|
||||
/*
|
||||
stroke-opacity: .65;
|
||||
fill-opacity: 1;
|
||||
*/
|
||||
stroke-opacity: 0;
|
||||
|
||||
transition: fill-opacity 250ms linear, stroke-opacity 250ms linear;
|
||||
-moz-transition: fill-opacity 250ms linear, stroke-opacity 250ms linear;
|
||||
-webkit-transition: fill-opacity 250ms linear, stroke-opacity 250ms linear;
|
||||
|
||||
/*
|
||||
transition-delay: 500ms;
|
||||
-moz-transition-delay: 500ms;
|
||||
-webkit-transition-delay: 500ms;
|
||||
*/
|
||||
|
||||
}
|
||||
|
||||
.nvd3.nv-stackedarea path.nv-area.hover {
|
||||
fill-opacity: .9;
|
||||
/*
|
||||
stroke-opacity: .85;
|
||||
*/
|
||||
}
|
||||
/*
|
||||
.d3stackedarea .groups path {
|
||||
stroke-opacity: 0;
|
||||
}
|
||||
*/
|
||||
|
||||
|
||||
|
||||
.nvd3.nv-stackedarea .nv-groups .nv-point {
|
||||
stroke-opacity: 0;
|
||||
fill-opacity: 0;
|
||||
}
|
||||
|
||||
.nvd3.nv-stackedarea .nv-groups .nv-point.hover {
|
||||
stroke-width: 20px;
|
||||
stroke-opacity: .75;
|
||||
fill-opacity: 1;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**********
|
||||
* Line Plus Bar
|
||||
*/
|
||||
|
||||
.nvd3.nv-linePlusBar .nv-bar rect {
|
||||
fill-opacity: .75;
|
||||
}
|
||||
|
||||
.nvd3.nv-linePlusBar .nv-bar rect:hover {
|
||||
fill-opacity: 1;
|
||||
}
|
||||
|
||||
|
||||
/**********
|
||||
* Bullet
|
||||
*/
|
||||
|
||||
.nvd3.nv-bullet { font: 10px sans-serif; }
|
||||
.nvd3.nv-bullet rect { fill-opacity: .6; }
|
||||
.nvd3.nv-bullet rect:hover { fill-opacity: 1; }
|
||||
.nvd3.nv-bullet .nv-marker { stroke: #000; stroke-width: 2px; }
|
||||
.nvd3.nv-bullet .nv-markerTriangle { stroke: #000; fill: #fff; stroke-width: 1.5px; }
|
||||
.nvd3.nv-bullet .nv-tick line { stroke: #666; stroke-width: .5px; }
|
||||
.nvd3.nv-bullet .nv-range.nv-s0 { fill: #eee; }
|
||||
.nvd3.nv-bullet .nv-range.nv-s1 { fill: #ddd; }
|
||||
.nvd3.nv-bullet .nv-range.nv-s2 { fill: #ccc; }
|
||||
.nvd3.nv-bullet .nv-title { font-size: 14px; font-weight: bold; }
|
||||
.nvd3.nv-bullet .nv-subtitle { fill: #999; }
|
||||
|
||||
|
||||
|
||||
|
||||
/**********
|
||||
* Sparkline
|
||||
*/
|
||||
|
||||
.nvd3.nv-sparkline path {
|
||||
fill: none;
|
||||
}
|
||||
|
||||
.nvd3.nv-sparklineplus g.nv-hoverValue {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.nvd3.nv-sparklineplus .nv-hoverValue line {
|
||||
stroke: #f44;
|
||||
stroke-width: 1.5px;
|
||||
}
|
||||
|
||||
.nvd3.nv-sparklineplus,
|
||||
.nvd3.nv-sparklineplus g {
|
||||
pointer-events: all;
|
||||
}
|
||||
|
||||
.nvd3 .nv-hoverArea {
|
||||
fill-opacity: 0;
|
||||
stroke-opacity: 0;
|
||||
}
|
||||
|
||||
.nvd3.nv-sparklineplus .nv-xValue,
|
||||
.nvd3.nv-sparklineplus .nv-yValue {
|
||||
/*
|
||||
stroke: #666;
|
||||
*/
|
||||
stroke-width: 0;
|
||||
font-size: .8em;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
.nvd3.nv-sparklineplus .nv-yValue {
|
||||
stroke: #f66;
|
||||
}
|
||||
|
||||
.nvd3.nv-sparklineplus .nv-maxValue {
|
||||
stroke: #2ca02c;
|
||||
fill: #2ca02c;
|
||||
}
|
||||
|
||||
.nvd3.nv-sparklineplus .nv-minValue {
|
||||
stroke: #d62728;
|
||||
fill: #d62728;
|
||||
}
|
||||
|
||||
.nvd3.nv-sparklineplus .nv-currentValue {
|
||||
stroke: #444;
|
||||
fill: #444;
|
||||
}
|
||||
|
||||
/**********
|
||||
* historical stock
|
||||
*/
|
||||
|
||||
.nvd3.nv-ohlcBar .nv-ticks .nv-tick {
|
||||
stroke-width: 2px;
|
||||
}
|
||||
|
||||
.nvd3.nv-ohlcBar .nv-ticks .nv-tick.hover {
|
||||
stroke-width: 4px;
|
||||
}
|
||||
|
||||
.nvd3.nv-ohlcBar .nv-ticks .nv-tick.positive {
|
||||
stroke: #2ca02c;
|
||||
}
|
||||
|
||||
.nvd3.nv-ohlcBar .nv-ticks .nv-tick.negative {
|
||||
stroke: #d62728;
|
||||
}
|
||||
|
||||
.nvd3.nv-historicalStockChart .nv-axis .nv-axislabel {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.nvd3.nv-historicalStockChart .nv-dragTarget {
|
||||
fill-opacity: 0;
|
||||
stroke: none;
|
||||
cursor: move;
|
||||
}
|
||||
|
||||
.nvd3 .nv-brush .extent {
|
||||
/*
|
||||
cursor: ew-resize !important;
|
||||
*/
|
||||
fill-opacity: 0 !important;
|
||||
}
|
||||
|
||||
.nvd3 .nv-brushBackground rect {
|
||||
stroke: #000;
|
||||
stroke-width: .4;
|
||||
fill: #fff;
|
||||
fill-opacity: .7;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**********
|
||||
* Indented Tree
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* TODO: the following 3 selectors are based on classes used in the example. I should either make them standard and leave them here, or move to a CSS file not included in the library
|
||||
*/
|
||||
.nvd3.nv-indentedtree .name {
|
||||
margin-left: 5px;
|
||||
}
|
||||
|
||||
.nvd3.nv-indentedtree .clickable {
|
||||
color: #08C;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.nvd3.nv-indentedtree span.clickable:hover {
|
||||
color: #005580;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
|
||||
.nvd3.nv-indentedtree .nv-childrenCount {
|
||||
display: inline-block;
|
||||
margin-left: 5px;
|
||||
}
|
||||
|
||||
.nvd3.nv-indentedtree .nv-treeicon {
|
||||
cursor: pointer;
|
||||
/*
|
||||
cursor: n-resize;
|
||||
*/
|
||||
}
|
||||
|
||||
.nvd3.nv-indentedtree .nv-treeicon.nv-folded {
|
||||
cursor: pointer;
|
||||
/*
|
||||
cursor: s-resize;
|
||||
*/
|
||||
}
|
||||
|
||||
|
7033
layerindex/static/js/d3.v2.js
vendored
Normal file
7033
layerindex/static/js/d3.v2.js
vendored
Normal file
File diff suppressed because it is too large
Load Diff
10563
layerindex/static/js/nv.d3.js
Normal file
10563
layerindex/static/js/nv.d3.js
Normal file
File diff suppressed because it is too large
Load Diff
207
layerindex/tools/import_classic.py
Executable file
207
layerindex/tools/import_classic.py
Executable file
|
@ -0,0 +1,207 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
# Import OE-Classic recipe data into the layer index database
|
||||
#
|
||||
# Copyright (C) 2013 Intel Corporation
|
||||
# Author: Paul Eggleton <paul.eggleton@linux.intel.com>
|
||||
#
|
||||
# Licensed under the MIT license, see COPYING.MIT for details
|
||||
|
||||
|
||||
import sys
|
||||
import os.path
|
||||
|
||||
sys.path.insert(0, os.path.realpath(os.path.join(os.path.dirname(__file__), '..')))
|
||||
|
||||
import optparse
|
||||
import logging
|
||||
from datetime import datetime
|
||||
import fnmatch
|
||||
import re
|
||||
import tempfile
|
||||
import shutil
|
||||
from distutils.version import LooseVersion
|
||||
import utils
|
||||
import recipeparse
|
||||
|
||||
logger = utils.logger_create('LayerIndexUpdate')
|
||||
|
||||
|
||||
def update_recipe_file(data, path, recipe, layerdir_start, repodir):
|
||||
fn = str(os.path.join(path, recipe.filename))
|
||||
try:
|
||||
logger.debug('Updating recipe %s' % fn)
|
||||
envdata = bb.cache.Cache.loadDataFull(fn, [], data)
|
||||
envdata.setVar('SRCPV', 'X')
|
||||
envdata.setVar('SRCDATE', 'X')
|
||||
envdata.setVar('SRCREV', 'X')
|
||||
envdata.setVar('OPIE_SRCREV', 'X')
|
||||
recipe.pn = envdata.getVar("PN", True)
|
||||
recipe.pv = envdata.getVar("PV", True)
|
||||
recipe.summary = envdata.getVar("SUMMARY", True)
|
||||
recipe.description = envdata.getVar("DESCRIPTION", True)
|
||||
recipe.section = envdata.getVar("SECTION", True)
|
||||
recipe.license = envdata.getVar("LICENSE", True)
|
||||
recipe.homepage = envdata.getVar("HOMEPAGE", True)
|
||||
recipe.provides = envdata.getVar("PROVIDES", True) or ""
|
||||
recipe.bbclassextend = envdata.getVar("BBCLASSEXTEND", True) or ""
|
||||
recipe.save()
|
||||
except KeyboardInterrupt:
|
||||
raise
|
||||
except BaseException as e:
|
||||
if not recipe.pn:
|
||||
recipe.pn = recipe.filename[:-3].split('_')[0]
|
||||
logger.error("Unable to read %s: %s", fn, str(e))
|
||||
|
||||
def main():
|
||||
|
||||
parser = optparse.OptionParser(
|
||||
usage = """
|
||||
%prog [options] <bitbakepath> <oeclassicpath>""")
|
||||
|
||||
parser.add_option("-b", "--branch",
|
||||
help = "Specify branch to import into",
|
||||
action="store", dest="branch", default='oe-classic')
|
||||
parser.add_option("-l", "--layer",
|
||||
help = "Specify layer to import into",
|
||||
action="store", dest="layer", default='oe-classic')
|
||||
parser.add_option("-n", "--dry-run",
|
||||
help = "Don't write any data back to the database",
|
||||
action="store_true", dest="dryrun")
|
||||
parser.add_option("-d", "--debug",
|
||||
help = "Enable debug output",
|
||||
action="store_const", const=logging.DEBUG, dest="loglevel", default=logging.INFO)
|
||||
parser.add_option("-q", "--quiet",
|
||||
help = "Hide all output except error messages",
|
||||
action="store_const", const=logging.ERROR, dest="loglevel")
|
||||
|
||||
|
||||
options, args = parser.parse_args(sys.argv)
|
||||
if len(args) < 3:
|
||||
logger.error('You must specify bitbakepath and oeclassicpath')
|
||||
parser.print_help()
|
||||
sys.exit(1)
|
||||
if len(args) > 3:
|
||||
logger.error('unexpected argument "%s"' % args[3])
|
||||
parser.print_help()
|
||||
sys.exit(1)
|
||||
|
||||
utils.setup_django()
|
||||
import settings
|
||||
from layerindex.models import LayerItem, LayerBranch, Recipe, ClassicRecipe, Machine, BBAppend, BBClass
|
||||
from django.db import transaction
|
||||
|
||||
logger.setLevel(options.loglevel)
|
||||
|
||||
branch = utils.get_branch(options.branch)
|
||||
if not branch:
|
||||
logger.error("Specified branch %s is not valid" % options.branch)
|
||||
sys.exit(1)
|
||||
|
||||
res = list(LayerItem.objects.filter(name=options.layer)[:1])
|
||||
if res:
|
||||
layer = res[0]
|
||||
else:
|
||||
layer = LayerItem()
|
||||
layer.name = options.layer
|
||||
layer.status = 'P'
|
||||
layer.layer_type = 'M'
|
||||
layer.summary = 'OE-Classic'
|
||||
layer.description = 'OpenEmbedded-Classic'
|
||||
layer.vcs_url = 'git://git.openembedded.org/openembedded'
|
||||
layer.vcs_web_url = 'http://cgit.openembedded.org/cgit.cgi/openembedded'
|
||||
layer.vcs_web_tree_base_url = 'http://cgit.openembedded.org/cgit.cgi/openembedded/tree/%path%'
|
||||
layer.vcs_web_file_base_url = 'http://cgit.openembedded.org/cgit.cgi/openembedded/tree/%path%'
|
||||
layer.classic = True
|
||||
layer.save()
|
||||
|
||||
layerbranch = layer.get_layerbranch(options.branch)
|
||||
if not layerbranch:
|
||||
# LayerBranch doesn't exist for this branch, create it
|
||||
layerbranch = LayerBranch()
|
||||
layerbranch.layer = layer
|
||||
layerbranch.branch = branch
|
||||
layerbranch.save()
|
||||
|
||||
fetchdir = settings.LAYER_FETCH_DIR
|
||||
if not fetchdir:
|
||||
logger.error("Please set LAYER_FETCH_DIR in settings.py")
|
||||
sys.exit(1)
|
||||
|
||||
if not os.path.exists(fetchdir):
|
||||
os.makedirs(fetchdir)
|
||||
fetchedrepos = []
|
||||
failedrepos = []
|
||||
|
||||
bitbakepath = args[1]
|
||||
oeclassicpath = args[2]
|
||||
|
||||
confparentdir = os.path.abspath(os.path.join(os.path.dirname(os.path.abspath(__file__)), '../../oe-classic'))
|
||||
os.environ['BBPATH'] = str("%s:%s" % (confparentdir, oeclassicpath))
|
||||
try:
|
||||
(tinfoil, tempdir) = recipeparse.init_parser(settings, branch, bitbakepath, nocheckout=True, classic=True)
|
||||
except recipeparse.RecipeParseError as e:
|
||||
logger.error(str(e))
|
||||
sys.exit(1)
|
||||
|
||||
# Clear the default value of SUMMARY so that we can use DESCRIPTION instead if it hasn't been set
|
||||
tinfoil.config_data.setVar('SUMMARY', '')
|
||||
# Clear the default value of DESCRIPTION so that we can see where it's not set
|
||||
tinfoil.config_data.setVar('DESCRIPTION', '')
|
||||
# Clear the default value of HOMEPAGE ('unknown')
|
||||
tinfoil.config_data.setVar('HOMEPAGE', '')
|
||||
|
||||
transaction.enter_transaction_management()
|
||||
transaction.managed(True)
|
||||
try:
|
||||
layerdir_start = os.path.normpath(oeclassicpath) + os.sep
|
||||
layerrecipes = Recipe.objects.filter(layerbranch=layerbranch)
|
||||
layermachines = Machine.objects.filter(layerbranch=layerbranch)
|
||||
layerappends = BBAppend.objects.filter(layerbranch=layerbranch)
|
||||
layerclasses = BBClass.objects.filter(layerbranch=layerbranch)
|
||||
|
||||
try:
|
||||
config_data_copy = recipeparse.setup_layer(tinfoil.config_data, fetchdir, oeclassicpath, layer, layerbranch)
|
||||
except recipeparse.RecipeParseError as e:
|
||||
logger.error(str(e))
|
||||
transaction.rollback()
|
||||
sys.exit(1)
|
||||
|
||||
layerrecipes.delete()
|
||||
layermachines.delete()
|
||||
layerappends.delete()
|
||||
layerclasses.delete()
|
||||
for root, dirs, files in os.walk(oeclassicpath):
|
||||
if '.git' in dirs:
|
||||
dirs.remove('.git')
|
||||
for f in files:
|
||||
fullpath = os.path.join(root, f)
|
||||
(typename, filepath, filename) = recipeparse.detect_file_type(fullpath, layerdir_start)
|
||||
if typename == 'recipe':
|
||||
recipe = ClassicRecipe()
|
||||
recipe.layerbranch = layerbranch
|
||||
recipe.filename = filename
|
||||
recipe.filepath = filepath
|
||||
update_recipe_file(config_data_copy, root, recipe, layerdir_start, oeclassicpath)
|
||||
recipe.save()
|
||||
|
||||
layerbranch.vcs_last_fetch = datetime.now()
|
||||
layerbranch.save()
|
||||
|
||||
if options.dryrun:
|
||||
transaction.rollback()
|
||||
else:
|
||||
transaction.commit()
|
||||
except:
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
transaction.rollback()
|
||||
finally:
|
||||
transaction.leave_transaction_management()
|
||||
|
||||
shutil.rmtree(tempdir)
|
||||
sys.exit(0)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
203
layerindex/tools/import_classic_wiki.py
Normal file
203
layerindex/tools/import_classic_wiki.py
Normal file
|
@ -0,0 +1,203 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
# Import migration info from OE-Classic recipes wiki page into OE
|
||||
# layer index database
|
||||
#
|
||||
# Copyright (C) 2013 Intel Corporation
|
||||
# Author: Paul Eggleton <paul.eggleton@linux.intel.com>
|
||||
#
|
||||
# Licensed under the MIT license, see COPYING.MIT for details
|
||||
|
||||
import sys
|
||||
import os.path
|
||||
|
||||
sys.path.insert(0, os.path.realpath(os.path.join(os.path.dirname(__file__), '..')))
|
||||
|
||||
import optparse
|
||||
import re
|
||||
import utils
|
||||
import logging
|
||||
|
||||
logger = utils.logger_create('LayerIndexImport')
|
||||
|
||||
|
||||
def read_page(site, path):
|
||||
ret = {}
|
||||
import httplib
|
||||
conn = httplib.HTTPConnection(site)
|
||||
conn.request("GET", path)
|
||||
resp = conn.getresponse()
|
||||
if resp.status in [200, 302]:
|
||||
data = resp.read()
|
||||
in_table = False
|
||||
for line in data.splitlines():
|
||||
if line.startswith('{|'):
|
||||
in_table = True
|
||||
continue
|
||||
if in_table:
|
||||
if line.startswith('|}'):
|
||||
# We're done
|
||||
in_table = False
|
||||
continue
|
||||
elif line.startswith('!'):
|
||||
pass
|
||||
elif not line.startswith('|-'):
|
||||
if line.startswith("|| ''"):
|
||||
continue
|
||||
fields = line.split('||')
|
||||
pn = fields[0].strip('|[]').split()[1]
|
||||
comment = fields[1]
|
||||
if comment:
|
||||
ret[pn] = comment
|
||||
else:
|
||||
logger.error('Fetch failed: %d: %s' % (resp.status, resp.reason))
|
||||
return ret
|
||||
|
||||
def main():
|
||||
|
||||
parser = optparse.OptionParser(
|
||||
usage = """
|
||||
%prog [options]""")
|
||||
|
||||
parser.add_option("-b", "--branch",
|
||||
help = "Specify branch to import into",
|
||||
action="store", dest="branch", default='oe-classic')
|
||||
parser.add_option("-l", "--layer",
|
||||
help = "Specify layer to import into",
|
||||
action="store", dest="layer", default='oe-classic')
|
||||
parser.add_option("-n", "--dry-run",
|
||||
help = "Don't write any data back to the database",
|
||||
action="store_true", dest="dryrun")
|
||||
parser.add_option("-d", "--debug",
|
||||
help = "Enable debug output",
|
||||
action="store_const", const=logging.DEBUG, dest="loglevel", default=logging.INFO)
|
||||
parser.add_option("-q", "--quiet",
|
||||
help = "Hide all output except error messages",
|
||||
action="store_const", const=logging.ERROR, dest="loglevel")
|
||||
|
||||
options, args = parser.parse_args(sys.argv)
|
||||
|
||||
utils.setup_django()
|
||||
from layerindex.models import LayerItem, LayerBranch, Recipe, ClassicRecipe
|
||||
from django.db import transaction
|
||||
|
||||
logger.setLevel(options.loglevel)
|
||||
|
||||
res = list(LayerItem.objects.filter(name=options.layer)[:1])
|
||||
if res:
|
||||
layer = res[0]
|
||||
else:
|
||||
logger.error('Specified layer %s does not exist in database' % options.layer)
|
||||
sys.exit(1)
|
||||
|
||||
layerbranch = layer.get_layerbranch(options.branch)
|
||||
if not layerbranch:
|
||||
logger.error("Specified branch %s does not exist in database" % options.branch)
|
||||
sys.exit(1)
|
||||
|
||||
recipes_ai = read_page("www.openembedded.org", "/wiki/OE-Classic_Recipes_A-I?action=raw")
|
||||
recipes_jz = read_page("www.openembedded.org", "/wiki/OE-Classic_Recipes_J-Z?action=raw")
|
||||
|
||||
transaction.enter_transaction_management()
|
||||
transaction.managed(True)
|
||||
try:
|
||||
recipes = dict(list(recipes_ai.items()) + list(recipes_jz.items()))
|
||||
for pn, comment in recipes.items():
|
||||
newpn = ''
|
||||
newlayer = ''
|
||||
status = 'U'
|
||||
comment = comment.strip(' -')
|
||||
if 'provided by' in comment:
|
||||
res = re.match(r'[a-zA-Z- ]*provided by ([a-zA-Z0-9-]*) in ([a-zA-Z0-9-]*)(.*)', comment)
|
||||
if res:
|
||||
newpn = res.group(1)
|
||||
newlayer = res.group(2)
|
||||
comment = res.group(3)
|
||||
if pn.endswith('-native') or pn.endswith('-cross'):
|
||||
status = 'P'
|
||||
else:
|
||||
status = 'R'
|
||||
elif 'replaced by' in comment or 'renamed to' in comment or ' is in ' in comment:
|
||||
res = re.match(r'.*replaced by ([a-zA-Z0-9-.]*) in ([a-zA-Z0-9-]*)(.*)', comment)
|
||||
if not res:
|
||||
res = re.match(r'.*renamed to ([a-zA-Z0-9-.]*) in ([a-zA-Z0-9-]*)(.*)', comment)
|
||||
if not res:
|
||||
res = re.match(r'([a-zA-Z0-9-.]*) is in ([a-zA-Z0-9-]*)(.*)', comment)
|
||||
if res:
|
||||
newpn = res.group(1)
|
||||
newlayer = res.group(2)
|
||||
comment = res.group(3)
|
||||
status = 'R'
|
||||
elif 'obsolete' in comment or 'superseded' in comment:
|
||||
res = re.match(r'provided by ([a-zA-Z0-9-]*) in ([a-zA-Z0-9-]*)(.*)', comment)
|
||||
if res:
|
||||
newpn = res.group(1)
|
||||
newlayer = res.group(2)
|
||||
comment = res.group(3)
|
||||
elif comment.startswith('superseded by'):
|
||||
comment = comment[14:]
|
||||
elif comment.startswith('obsolete'):
|
||||
comment = comment[9:]
|
||||
status = 'O'
|
||||
elif 'PACKAGECONFIG' in comment:
|
||||
res = re.match(r'[a-zA-Z ]* PACKAGECONFIG [a-zA-Z ]* to ([a-zA-Z0-9-]*) in ([a-zA-Z0-9-]*)(.*)', comment)
|
||||
if res:
|
||||
newpn = res.group(1)
|
||||
newlayer = res.group(2)
|
||||
comment = res.group(3)
|
||||
status = 'C'
|
||||
|
||||
if newlayer:
|
||||
if newlayer.lower() == 'oe-core':
|
||||
newlayer = 'openembedded-core'
|
||||
|
||||
# Remove all links from comments because they'll be picked up as categories
|
||||
comment = re.sub(r'\[(http[^[]*)\]', r'\1', comment)
|
||||
# Split out categories
|
||||
categories = re.findall(r'\[([^]]*)\]', comment)
|
||||
for cat in categories:
|
||||
comment = comment.replace('[%s]' % cat, '')
|
||||
if '(GPE)' in comment or pn.startswith('gpe'):
|
||||
categories.append('GPE')
|
||||
comment = comment.replace('(GPE)', '')
|
||||
|
||||
comment = comment.strip('- ')
|
||||
|
||||
logger.debug("%s|%s|%s|%s|%s|%s" % (pn, status, newpn, newlayer, categories, comment))
|
||||
|
||||
recipequery = ClassicRecipe.objects.filter(layerbranch=layerbranch).filter(pn=pn)
|
||||
if recipequery:
|
||||
for recipe in recipequery:
|
||||
recipe.cover_layerbranch = None
|
||||
if newlayer:
|
||||
res = list(LayerItem.objects.filter(name=newlayer)[:1])
|
||||
if res:
|
||||
newlayeritem = res[0]
|
||||
recipe.cover_layerbranch = newlayeritem.get_layerbranch('master')
|
||||
else:
|
||||
logger.info('Replacement layer "%s" for %s could not be found' % (newlayer, pn))
|
||||
recipe.cover_pn = newpn
|
||||
recipe.cover_status = status
|
||||
recipe.cover_verified = True
|
||||
recipe.cover_comment = comment
|
||||
recipe.classic_category = " ".join(categories)
|
||||
recipe.save()
|
||||
else:
|
||||
logger.info('No OE-Classic recipe with name "%s" count be found' % pn)
|
||||
sys.exit(1)
|
||||
|
||||
if options.dryrun:
|
||||
transaction.rollback()
|
||||
else:
|
||||
transaction.commit()
|
||||
except:
|
||||
transaction.rollback()
|
||||
raise
|
||||
finally:
|
||||
transaction.leave_transaction_management()
|
||||
|
||||
sys.exit(0)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
125
layerindex/tools/update_classic_status.py
Normal file
125
layerindex/tools/update_classic_status.py
Normal file
|
@ -0,0 +1,125 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
# Update migration info in OE-Classic recipes in OE layer index database
|
||||
#
|
||||
# Copyright (C) 2013 Intel Corporation
|
||||
# Author: Paul Eggleton <paul.eggleton@linux.intel.com>
|
||||
#
|
||||
# Licensed under the MIT license, see COPYING.MIT for details
|
||||
|
||||
import sys
|
||||
import os.path
|
||||
|
||||
sys.path.insert(0, os.path.realpath(os.path.join(os.path.dirname(__file__), '..')))
|
||||
|
||||
import optparse
|
||||
import re
|
||||
import utils
|
||||
import logging
|
||||
|
||||
logger = utils.logger_create('LayerIndexClassicUpdate')
|
||||
|
||||
|
||||
def main():
|
||||
|
||||
parser = optparse.OptionParser(
|
||||
usage = """
|
||||
%prog [options]""")
|
||||
|
||||
parser.add_option("-b", "--branch",
|
||||
help = "Specify branch to import into",
|
||||
action="store", dest="branch", default='oe-classic')
|
||||
parser.add_option("-l", "--layer",
|
||||
help = "Specify layer to import into",
|
||||
action="store", dest="layer", default='oe-classic')
|
||||
parser.add_option("-n", "--dry-run",
|
||||
help = "Don't write any data back to the database",
|
||||
action="store_true", dest="dryrun")
|
||||
parser.add_option("-d", "--debug",
|
||||
help = "Enable debug output",
|
||||
action="store_const", const=logging.DEBUG, dest="loglevel", default=logging.INFO)
|
||||
parser.add_option("-q", "--quiet",
|
||||
help = "Hide all output except error messages",
|
||||
action="store_const", const=logging.ERROR, dest="loglevel")
|
||||
|
||||
options, args = parser.parse_args(sys.argv)
|
||||
|
||||
utils.setup_django()
|
||||
from layerindex.models import LayerItem, LayerBranch, Recipe, ClassicRecipe
|
||||
from django.db import transaction
|
||||
|
||||
logger.setLevel(options.loglevel)
|
||||
|
||||
res = list(LayerItem.objects.filter(name=options.layer)[:1])
|
||||
if res:
|
||||
layer = res[0]
|
||||
else:
|
||||
logger.error('Specified layer %s does not exist in database' % options.layer)
|
||||
sys.exit(1)
|
||||
|
||||
layerbranch = layer.get_layerbranch(options.branch)
|
||||
if not layerbranch:
|
||||
logger.error("Specified branch %s does not exist in database" % options.branch)
|
||||
sys.exit(1)
|
||||
|
||||
transaction.enter_transaction_management()
|
||||
transaction.managed(True)
|
||||
try:
|
||||
def recipe_pn_query(pn):
|
||||
return Recipe.objects.filter(layerbranch__branch__name='master').filter(pn=pn).order_by('layerbranch__layer__index_preference')
|
||||
|
||||
recipequery = ClassicRecipe.objects.filter(layerbranch=layerbranch).filter(cover_status__in=['U', 'N'])
|
||||
for recipe in recipequery:
|
||||
replquery = recipe_pn_query(recipe.pn)
|
||||
found = False
|
||||
for replrecipe in replquery:
|
||||
logger.debug('Matched %s in layer %s' % (recipe.pn, replrecipe.layerbranch.layer.name))
|
||||
recipe.cover_layerbranch = replrecipe.layerbranch
|
||||
recipe.cover_status = 'D'
|
||||
recipe.cover_verified = False
|
||||
recipe.save()
|
||||
found = True
|
||||
break
|
||||
if not found:
|
||||
if recipe.pn.endswith('-native') or recipe.pn.endswith('-nativesdk'):
|
||||
searchpn, _, suffix = recipe.pn.rpartition('-')
|
||||
replquery = recipe_pn_query(searchpn)
|
||||
for replrecipe in replquery:
|
||||
if suffix in replrecipe.bbclassextend.split():
|
||||
logger.debug('Found BBCLASSEXTEND of %s to cover %s in layer %s' % (replrecipe.pn, recipe.pn, replrecipe.layerbranch.layer.name))
|
||||
recipe.cover_layerbranch = replrecipe.layerbranch
|
||||
recipe.cover_pn = replrecipe.pn
|
||||
recipe.cover_status = 'P'
|
||||
recipe.cover_verified = False
|
||||
recipe.save()
|
||||
found = True
|
||||
break
|
||||
if not found and recipe.pn.endswith('-nativesdk'):
|
||||
searchpn, _, _ = recipe.pn.rpartition('-')
|
||||
replquery = recipe_pn_query('nativesdk-%s' % searchpn)
|
||||
for replrecipe in replquery:
|
||||
logger.debug('Found replacement %s to cover %s in layer %s' % (replrecipe.pn, recipe.pn, replrecipe.layerbranch.layer.name))
|
||||
recipe.cover_layerbranch = replrecipe.layerbranch
|
||||
recipe.cover_pn = replrecipe.pn
|
||||
recipe.cover_status = 'R'
|
||||
recipe.cover_verified = False
|
||||
recipe.save()
|
||||
found = True
|
||||
break
|
||||
|
||||
|
||||
if options.dryrun:
|
||||
transaction.rollback()
|
||||
else:
|
||||
transaction.commit()
|
||||
except:
|
||||
transaction.rollback()
|
||||
raise
|
||||
finally:
|
||||
transaction.leave_transaction_management()
|
||||
|
||||
sys.exit(0)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
|
@ -13,7 +13,6 @@ import os.path
|
|||
import optparse
|
||||
import logging
|
||||
from datetime import datetime
|
||||
import fnmatch
|
||||
import re
|
||||
import tempfile
|
||||
import shutil
|
||||
|
@ -31,37 +30,6 @@ except ImportError:
|
|||
sys.exit(1)
|
||||
|
||||
|
||||
machine_conf_re = re.compile(r'conf/machine/([^/.]*).conf$')
|
||||
bbclass_re = re.compile(r'classes/([^/.]*).bbclass$')
|
||||
def detect_file_type(path, subdir_start):
|
||||
typename = None
|
||||
if fnmatch.fnmatch(path, "*.bb"):
|
||||
typename = 'recipe'
|
||||
elif fnmatch.fnmatch(path, "*.bbappend"):
|
||||
typename = 'bbappend'
|
||||
else:
|
||||
# Check if it's a machine conf file
|
||||
subpath = path[len(subdir_start):]
|
||||
res = machine_conf_re.match(subpath)
|
||||
if res:
|
||||
typename = 'machine'
|
||||
return (typename, None, res.group(1))
|
||||
else:
|
||||
res = bbclass_re.match(subpath)
|
||||
if res:
|
||||
typename = 'bbclass'
|
||||
return (typename, None, res.group(1))
|
||||
|
||||
if typename == 'recipe' or typename == 'bbappend':
|
||||
if subdir_start:
|
||||
filepath = os.path.relpath(os.path.dirname(path), subdir_start)
|
||||
else:
|
||||
filepath = os.path.dirname(path)
|
||||
return (typename, filepath, os.path.basename(path))
|
||||
|
||||
return (None, None, None)
|
||||
|
||||
|
||||
def check_machine_conf(path, subdir_start):
|
||||
subpath = path[len(subdir_start):]
|
||||
res = conf_re.match(subpath)
|
||||
|
@ -191,12 +159,12 @@ def main():
|
|||
sys.exit(1)
|
||||
|
||||
if options.layers:
|
||||
layerquery = LayerItem.objects.filter(name__in=options.layers.split(','))
|
||||
layerquery = LayerItem.objects.filter(classic=False).filter(name__in=options.layers.split(','))
|
||||
if layerquery.count() == 0:
|
||||
logger.error('No layers matching specified query "%s"' % options.layers)
|
||||
sys.exit(1)
|
||||
else:
|
||||
layerquery = LayerItem.objects.filter(status='P')
|
||||
layerquery = LayerItem.objects.filter(classic=False).filter(status='P')
|
||||
if layerquery.count() == 0:
|
||||
logger.info("No published layers to update")
|
||||
sys.exit(1)
|
||||
|
@ -379,7 +347,7 @@ def main():
|
|||
for d in diff.iter_change_type('D'):
|
||||
path = d.a_blob.path
|
||||
if path.startswith(subdir_start):
|
||||
(typename, filepath, filename) = detect_file_type(path, subdir_start)
|
||||
(typename, filepath, filename) = recipeparse.detect_file_type(path, subdir_start)
|
||||
if typename == 'recipe':
|
||||
values = layerrecipes.filter(filepath=filepath).filter(filename=filename).values('id', 'filepath', 'filename', 'pn')
|
||||
layerrecipes_delete.append(values[0])
|
||||
|
@ -395,7 +363,7 @@ def main():
|
|||
for d in diff.iter_change_type('A'):
|
||||
path = d.b_blob.path
|
||||
if path.startswith(subdir_start):
|
||||
(typename, filepath, filename) = detect_file_type(path, subdir_start)
|
||||
(typename, filepath, filename) = recipeparse.detect_file_type(path, subdir_start)
|
||||
if typename == 'recipe':
|
||||
layerrecipes_add.append(os.path.join(repodir, path))
|
||||
logger.debug("Mark %s for addition" % path)
|
||||
|
@ -422,7 +390,7 @@ def main():
|
|||
for d in diff.iter_change_type('M'):
|
||||
path = d.a_blob.path
|
||||
if path.startswith(subdir_start):
|
||||
(typename, filepath, filename) = detect_file_type(path, subdir_start)
|
||||
(typename, filepath, filename) = recipeparse.detect_file_type(path, subdir_start)
|
||||
if typename == 'recipe':
|
||||
logger.debug("Mark %s for update" % path)
|
||||
results = layerrecipes.filter(filepath=filepath).filter(filename=filename)[:1]
|
||||
|
@ -472,7 +440,7 @@ def main():
|
|||
dirs.remove('.git')
|
||||
for f in files:
|
||||
fullpath = os.path.join(root, f)
|
||||
(typename, _, filename) = detect_file_type(fullpath, layerdir_start)
|
||||
(typename, _, filename) = recipeparse.detect_file_type(fullpath, layerdir_start)
|
||||
if typename == 'recipe':
|
||||
if fullpath not in layerrecipe_fns:
|
||||
layerrecipes_add.append(fullpath)
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
from django.conf.urls.defaults import *
|
||||
from django.views.generic import TemplateView, DetailView, ListView
|
||||
from django.views.defaults import page_not_found
|
||||
from layerindex.views import LayerListView, LayerReviewListView, LayerReviewDetailView, RecipeSearchView, MachineSearchView, PlainTextListView, LayerDetailView, edit_layer_view, delete_layer_view, edit_layernote_view, delete_layernote_view, switch_branch_view, HistoryListView, EditProfileFormView, DuplicatesView, AdvancedRecipeSearchView, BulkChangeView, BulkChangeSearchView, bulk_change_edit_view, bulk_change_patch_view, BulkChangeDeleteView, RecipeDetailView
|
||||
from layerindex.views import LayerListView, LayerReviewListView, LayerReviewDetailView, RecipeSearchView, MachineSearchView, PlainTextListView, LayerDetailView, edit_layer_view, delete_layer_view, edit_layernote_view, delete_layernote_view, switch_branch_view, HistoryListView, EditProfileFormView, DuplicatesView, AdvancedRecipeSearchView, BulkChangeView, BulkChangeSearchView, bulk_change_edit_view, bulk_change_patch_view, BulkChangeDeleteView, RecipeDetailView, ClassicRecipeSearchView, ClassicRecipeDetailView, ClassicRecipeStatsView
|
||||
from layerindex.models import LayerItem, Recipe, RecipeChangeset
|
||||
|
||||
urlpatterns = patterns('',
|
||||
|
@ -104,5 +104,20 @@ urlpatterns = patterns('',
|
|||
TemplateView.as_view(
|
||||
template_name='layerindex/about.html'),
|
||||
name="about"),
|
||||
url(r'^oe-classic/$',
|
||||
redirect_to, {'url' : reverse_lazy('classic_recipe_search')},
|
||||
name='classic'),
|
||||
url(r'^oe-classic/recipes/$',
|
||||
ClassicRecipeSearchView.as_view(
|
||||
template_name='layerindex/classicrecipes.html'),
|
||||
name='classic_recipe_search'),
|
||||
url(r'^oe-classic/stats/$',
|
||||
ClassicRecipeStatsView.as_view(
|
||||
template_name='layerindex/classicstats.html'),
|
||||
name='classic_recipe_stats'),
|
||||
url(r'^oe-classic/recipe/(?P<pk>[-\w]+)/$',
|
||||
ClassicRecipeDetailView.as_view(
|
||||
template_name='layerindex/classicrecipedetail.html'),
|
||||
name='classic_recipe'),
|
||||
url(r'.*', page_not_found)
|
||||
)
|
||||
|
|
|
@ -5,15 +5,15 @@
|
|||
# Licensed under the MIT license, see COPYING.MIT for details
|
||||
|
||||
from django.shortcuts import get_object_or_404, render
|
||||
from django.http import HttpResponse, HttpResponseRedirect, HttpResponseForbidden
|
||||
from django.http import HttpResponse, HttpResponseRedirect, HttpResponseForbidden, Http404
|
||||
from django.core.urlresolvers import reverse, reverse_lazy
|
||||
from django.core.exceptions import PermissionDenied
|
||||
from django.template import RequestContext
|
||||
from layerindex.models import Branch, LayerItem, LayerMaintainer, LayerBranch, LayerDependency, LayerNote, Recipe, Machine, BBClass, BBAppend, RecipeChange, RecipeChangeset
|
||||
from layerindex.models import Branch, LayerItem, LayerMaintainer, LayerBranch, LayerDependency, LayerNote, Recipe, Machine, BBClass, BBAppend, RecipeChange, RecipeChangeset, ClassicRecipe
|
||||
from datetime import datetime
|
||||
from django.views.generic import TemplateView, DetailView, ListView
|
||||
from django.views.generic.edit import CreateView, DeleteView, UpdateView
|
||||
from layerindex.forms import EditLayerForm, LayerMaintainerFormSet, EditNoteForm, EditProfileForm, RecipeChangesetForm, AdvancedRecipeSearchForm, BulkChangeEditFormSet
|
||||
from layerindex.forms import EditLayerForm, LayerMaintainerFormSet, EditNoteForm, EditProfileForm, RecipeChangesetForm, AdvancedRecipeSearchForm, BulkChangeEditFormSet, ClassicRecipeForm, ClassicRecipeSearchForm
|
||||
from django.db import transaction
|
||||
from django.contrib.auth.models import User, Permission
|
||||
from django.db.models import Q, Count
|
||||
|
@ -32,6 +32,8 @@ import reversion
|
|||
|
||||
def edit_layernote_view(request, template_name, slug, pk=None):
|
||||
layeritem = get_object_or_404(LayerItem, name=slug)
|
||||
if layeritem.classic:
|
||||
raise Http404
|
||||
if not (request.user.is_authenticated() and (request.user.has_perm('layerindex.publish_layer') or layeritem.user_can_edit(request.user))):
|
||||
raise PermissionDenied
|
||||
if pk:
|
||||
|
@ -56,6 +58,8 @@ def edit_layernote_view(request, template_name, slug, pk=None):
|
|||
|
||||
def delete_layernote_view(request, template_name, slug, pk):
|
||||
layeritem = get_object_or_404(LayerItem, name=slug)
|
||||
if layeritem.classic:
|
||||
raise Http404
|
||||
if not (request.user.is_authenticated() and (request.user.has_perm('layerindex.publish_layer') or layeritem.user_can_edit(request.user))):
|
||||
raise PermissionDenied
|
||||
layernote = get_object_or_404(LayerNote, pk=pk)
|
||||
|
@ -71,6 +75,8 @@ def delete_layernote_view(request, template_name, slug, pk):
|
|||
|
||||
def delete_layer_view(request, template_name, slug):
|
||||
layeritem = get_object_or_404(LayerItem, name=slug)
|
||||
if layeritem.classic:
|
||||
raise Http404
|
||||
if not (request.user.is_authenticated() and request.user.has_perm('layerindex.publish_layer') and layeritem.status == 'N'):
|
||||
raise PermissionDenied
|
||||
if request.method == 'POST':
|
||||
|
@ -89,6 +95,8 @@ def edit_layer_view(request, template_name, slug=None):
|
|||
# Edit mode
|
||||
branch = Branch.objects.filter(name=request.session.get('branch', 'master'))[:1].get()
|
||||
layeritem = get_object_or_404(LayerItem, name=slug)
|
||||
if layeritem.classic:
|
||||
raise Http404
|
||||
if not (request.user.is_authenticated() and (request.user.has_perm('layerindex.publish_layer') or layeritem.user_can_edit(request.user))):
|
||||
raise PermissionDenied
|
||||
layerbranch = get_object_or_404(LayerBranch, layer=layeritem, branch=branch)
|
||||
|
@ -101,7 +109,7 @@ def edit_layer_view(request, template_name, slug=None):
|
|||
branch = Branch.objects.filter(name='master')[:1].get()
|
||||
layeritem = LayerItem()
|
||||
layerbranch = LayerBranch(layer=layeritem, branch=branch)
|
||||
deplistlayers = LayerItem.objects.all().order_by('name')
|
||||
deplistlayers = LayerItem.objects.filter(classic=False).order_by('name')
|
||||
|
||||
if request.method == 'POST':
|
||||
last_vcs_url = layeritem.vcs_url
|
||||
|
@ -249,6 +257,8 @@ def publish(request, name):
|
|||
|
||||
def _statuschange(request, name, newstatus):
|
||||
w = get_object_or_404(LayerItem, name=name)
|
||||
if w.classic:
|
||||
raise Http404
|
||||
if w.status != newstatus:
|
||||
w.change_status(newstatus, request.user.username)
|
||||
w.save()
|
||||
|
@ -287,6 +297,8 @@ class LayerDetailView(DetailView):
|
|||
res = super(LayerDetailView, self).dispatch(request, *args, **kwargs)
|
||||
l = self.get_object()
|
||||
if l:
|
||||
if l.classic:
|
||||
raise Http404
|
||||
if l.status == 'N':
|
||||
if not (request.user.is_authenticated() and request.user.has_perm('layerindex.publish_layer')):
|
||||
raise PermissionDenied
|
||||
|
@ -297,10 +309,11 @@ class LayerDetailView(DetailView):
|
|||
layer = context['layeritem']
|
||||
context['useredit'] = layer.user_can_edit(self.user)
|
||||
layerbranch = layer.get_layerbranch(self.request.session.get('branch', 'master'))
|
||||
context['layerbranch'] = layerbranch
|
||||
context['machines'] = layerbranch.machine_set.order_by('name')
|
||||
context['appends'] = layerbranch.bbappend_set.order_by('filename')
|
||||
context['classes'] = layerbranch.bbclass_set.order_by('name')
|
||||
if layerbranch:
|
||||
context['layerbranch'] = layerbranch
|
||||
context['machines'] = layerbranch.machine_set.order_by('name')
|
||||
context['appends'] = layerbranch.bbappend_set.order_by('filename')
|
||||
context['classes'] = layerbranch.bbclass_set.order_by('name')
|
||||
return context
|
||||
|
||||
class LayerReviewDetailView(LayerDetailView):
|
||||
|
@ -337,6 +350,7 @@ class RecipeSearchView(ListView):
|
|||
paginate_by = 50
|
||||
|
||||
def get_queryset(self):
|
||||
_check_branch(self.request)
|
||||
query_string = self.request.GET.get('q', '')
|
||||
init_qs = Recipe.objects.filter(layerbranch__branch__name=self.request.session.get('branch', 'master'))
|
||||
if query_string.strip():
|
||||
|
@ -607,3 +621,123 @@ class RecipeDetailView(DetailView):
|
|||
appendprefix = "%s_" % recipe.pn
|
||||
context['appends'] = BBAppend.objects.filter(layerbranch__branch__name=self.request.session.get('branch', 'master')).filter(filename__startswith=appendprefix)
|
||||
return context
|
||||
|
||||
|
||||
class ClassicRecipeSearchView(RecipeSearchView):
|
||||
def get_queryset(self):
|
||||
self.kwargs['branch'] = 'oe-classic'
|
||||
query_string = self.request.GET.get('q', '')
|
||||
cover_status = self.request.GET.get('cover_status', None)
|
||||
cover_verified = self.request.GET.get('cover_verified', None)
|
||||
category = self.request.GET.get('category', None)
|
||||
init_qs = ClassicRecipe.objects.filter(layerbranch__branch__name='oe-classic')
|
||||
if cover_status:
|
||||
if cover_status == '!':
|
||||
init_qs = init_qs.filter(cover_status__in=['U', 'N'])
|
||||
else:
|
||||
init_qs = init_qs.filter(cover_status=cover_status)
|
||||
if cover_verified:
|
||||
init_qs = init_qs.filter(cover_verified=(cover_verified=='1'))
|
||||
if category:
|
||||
init_qs = init_qs.filter(classic_category__icontains=category)
|
||||
if query_string.strip():
|
||||
entry_query = simplesearch.get_query(query_string, ['pn', 'summary', 'description', 'filename'])
|
||||
qs = init_qs.filter(entry_query).order_by('pn', 'layerbranch__layer')
|
||||
else:
|
||||
if 'q' in self.request.GET:
|
||||
qs = init_qs.order_by('pn', 'layerbranch__layer')
|
||||
else:
|
||||
# It's a bit too slow to return all records by default, and most people
|
||||
# won't actually want that (if they do they can just hit the search button
|
||||
# with no query string)
|
||||
return Recipe.objects.none()
|
||||
return qs
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(ClassicRecipeSearchView, self).get_context_data(**kwargs)
|
||||
context['multi_classic_layers'] = LayerItem.objects.filter(classic=True).count() > 1
|
||||
if 'q' in self.request.GET:
|
||||
searched = True
|
||||
search_form = ClassicRecipeSearchForm(self.request.GET)
|
||||
else:
|
||||
searched = False
|
||||
search_form = ClassicRecipeSearchForm()
|
||||
context['search_form'] = search_form
|
||||
context['searched'] = searched
|
||||
return context
|
||||
|
||||
|
||||
|
||||
class ClassicRecipeDetailView(UpdateView):
|
||||
model = ClassicRecipe
|
||||
form_class = ClassicRecipeForm
|
||||
context_object_name = 'recipe'
|
||||
|
||||
def _can_edit(self):
|
||||
if self.request.user.is_authenticated():
|
||||
if not self.request.user.has_perm('layerindex.edit_classic'):
|
||||
user_email = self.request.user.email.strip().lower()
|
||||
if not LayerMaintainer.objects.filter(email__iexact=user_email):
|
||||
return False
|
||||
else:
|
||||
return False
|
||||
return True
|
||||
|
||||
def post(self, request, *args, **kwargs):
|
||||
if not self._can_edit():
|
||||
raise PermissionDenied
|
||||
|
||||
return super(ClassicRecipeDetailView, self).post(request, *args, **kwargs)
|
||||
|
||||
def get_success_url(self):
|
||||
return reverse_lazy('classic_recipe_search')
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(ClassicRecipeDetailView, self).get_context_data(**kwargs)
|
||||
context['can_edit'] = self._can_edit()
|
||||
return context
|
||||
|
||||
|
||||
class ClassicRecipeStatsView(TemplateView):
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(ClassicRecipeStatsView, self).get_context_data(**kwargs)
|
||||
# *** Cover status chart ***
|
||||
statuses = []
|
||||
status_counts = {}
|
||||
for choice, desc in ClassicRecipe.COVER_STATUS_CHOICES:
|
||||
statuses.append(desc)
|
||||
status_counts[desc] = ClassicRecipe.objects.filter(cover_status=choice).count()
|
||||
statuses = sorted(statuses, key=lambda status: status_counts[status], reverse=True)
|
||||
chartdata = {'x': statuses, 'y': [status_counts[k] for k in statuses]}
|
||||
context['charttype_status'] = 'pieChart'
|
||||
context['chartdata_status'] = chartdata
|
||||
# *** Categories chart ***
|
||||
categories = ['obsoletedir', 'nonworkingdir']
|
||||
uniquevals = ClassicRecipe.objects.exclude(classic_category='').values_list('classic_category', flat=True).distinct()
|
||||
for value in uniquevals:
|
||||
cats = value.split()
|
||||
for cat in cats:
|
||||
if not cat in categories:
|
||||
categories.append(cat)
|
||||
categories.append('none')
|
||||
catcounts = dict.fromkeys(categories, 0)
|
||||
unmigrated = ClassicRecipe.objects.filter(cover_status='U')
|
||||
catcounts['none'] = unmigrated.filter(classic_category='').count()
|
||||
values = unmigrated.exclude(classic_category='').values_list('classic_category', flat=True)
|
||||
# We gather data this way because an item might be in more than one category, thus
|
||||
# the categories list must be in priority order
|
||||
for value in values:
|
||||
recipecats = value.split()
|
||||
foundcat = 'none'
|
||||
for cat in categories:
|
||||
if cat in recipecats:
|
||||
foundcat = cat
|
||||
break
|
||||
catcounts[foundcat] += 1
|
||||
# Eliminate categories with zero count
|
||||
categories = [cat for cat in categories if catcounts[cat] > 0]
|
||||
categories = sorted(categories, key=lambda cat: catcounts[cat], reverse=True)
|
||||
chartdata_category = {'x': categories, 'y': [catcounts[k] for k in categories]}
|
||||
context['charttype_category'] = 'pieChart'
|
||||
context['chartdata_category'] = chartdata_category
|
||||
return context
|
||||
|
|
15
oe-classic/conf/local.conf
Normal file
15
oe-classic/conf/local.conf
Normal file
|
@ -0,0 +1,15 @@
|
|||
#
|
||||
# This is a version of local.conf trimmed specially for parsing recipes
|
||||
# within the OE Layer index update script. Since we're not doing any
|
||||
# actual building and many variables are defaulted via bitbake.conf or
|
||||
# the "minimal" distro config, we don't need to set very much.
|
||||
|
||||
# Just select something basic here:
|
||||
MACHINE = "qemux86"
|
||||
|
||||
# OE-Classic basic distro configuration
|
||||
DISTRO = "minimal"
|
||||
|
||||
# Ensure we select a packaging backend so image recipes parse
|
||||
INHERIT += "package_ipk"
|
||||
|
|
@ -143,7 +143,8 @@ INSTALLED_APPS = (
|
|||
'reversion',
|
||||
'reversion_compare',
|
||||
'captcha',
|
||||
'south'
|
||||
'south',
|
||||
'django_nvd3'
|
||||
)
|
||||
|
||||
# A sample logging configuration. The only tangible logging
|
||||
|
|
|
@ -48,6 +48,9 @@
|
|||
{% if branch.name = current_branch %}</b>{% endif %}
|
||||
</a></li>
|
||||
{% endfor %}
|
||||
{% if oe_classic %}
|
||||
<li><a href="{% url classic %}">OE-Classic</a></li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</li>
|
||||
{% endblock %}
|
||||
|
|
|
@ -24,11 +24,13 @@
|
|||
<li><a href="http://github.com/etianen/django-reversion">django-reversion</a></li>
|
||||
<li><a href="http://pypi.python.org/pypi/django-reversion-compare/">django-reversion-compare</a></li>
|
||||
<li><a href="http://github.com/mbi/django-simple-captcha">django-simple-captcha</a></li>
|
||||
<li><a href="http://github.com/areski/django-nvd3">django-nvd3</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li><a href="http://twitter.github.com/bootstrap/">Twitter Bootstrap</a></li>
|
||||
<li><a href="http://glyphicons.com/">Glyphicons</a> (via Bootstrap)</li>
|
||||
<li><a href="http://jquery.com/">jQuery</a> (via Bootstrap)</li>
|
||||
<li><a href="http://nvd3.org/">NVD3</a></li>
|
||||
<li><a href="http://www.openembedded.org/wiki/BitBake_%28user%29">BitBake</a></li>
|
||||
</ul>
|
||||
|
||||
|
|
61
templates/layerindex/classic_base.html
Normal file
61
templates/layerindex/classic_base.html
Normal file
|
@ -0,0 +1,61 @@
|
|||
{% extends "base_toplevel.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% comment %}
|
||||
|
||||
layerindex-web - OE-Classic index page template
|
||||
|
||||
Copyright (C) 2013 Intel Corporation
|
||||
Licensed under the MIT license, see COPYING.MIT for details
|
||||
|
||||
{% endcomment %}
|
||||
|
||||
|
||||
<!--
|
||||
{% block title_append %} - OE-Classic{% endblock %}
|
||||
-->
|
||||
|
||||
{% block branch_selector %}
|
||||
{% autoescape on %}
|
||||
<li class="dropdown">
|
||||
<a href="#" class="dropdown-toggle" data-toggle="dropdown">
|
||||
Branch: <b>OE-Classic</b>
|
||||
<b class="caret"></b>
|
||||
</a>
|
||||
<ul class="dropdown-menu">
|
||||
{% for branch in all_branches %}
|
||||
<li><a href="{% url layer_list branch.name %}">
|
||||
{{ branch.name }}
|
||||
{% if branch.short_description %}
|
||||
({{ branch.short_description }})
|
||||
{% endif %}
|
||||
</a></li>
|
||||
{% endfor %}
|
||||
<li class="divider"></li>
|
||||
<li><a href="{% url classic %}"><b>OE-Classic</b></a></li>
|
||||
</ul>
|
||||
</li>
|
||||
{% endautoescape %}
|
||||
{% endblock %}
|
||||
|
||||
{% block content_inner %}
|
||||
{% autoescape on %}
|
||||
|
||||
<div class="container-fluid">
|
||||
<div class="row-fluid">
|
||||
|
||||
<h2>OE-Classic</h2>
|
||||
|
||||
<p>OpenEmbedded-Classic (OE-Classic) is the name for the old monolithic version of OpenEmbedded. It contained a number of recipes some of which have not yet been migrated on top of OE-Core. To help people to find and migrate these recipes we provide an index here as well as some statistics to get an idea of the migration.</p>
|
||||
|
||||
<a class="btn btn-large btn-primary" href="{% url classic_recipe_search %}">Recipes</a>
|
||||
<a class="btn btn-large" href="{% url classic_recipe_search %}?q=&cover_status=!">Unmigrated Recipes</a>
|
||||
<a class="btn btn-large btn-primary" href="{% url classic_recipe_stats %}">Stats</a>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
{% endautoescape %}
|
||||
|
||||
{% endblock %}
|
||||
|
165
templates/layerindex/classicrecipedetail.html
Normal file
165
templates/layerindex/classicrecipedetail.html
Normal file
|
@ -0,0 +1,165 @@
|
|||
{% extends "base.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% comment %}
|
||||
|
||||
layerindex-web - recipe detail page template
|
||||
|
||||
Copyright (C) 2013 Intel Corporation
|
||||
Licensed under the MIT license, see COPYING.MIT for details
|
||||
|
||||
{% endcomment %}
|
||||
|
||||
|
||||
<!--
|
||||
{% autoescape on %}
|
||||
{% block title_append %} - OE-Classic - {{ recipe.pn }}{% endblock %}
|
||||
{% endautoescape %}
|
||||
-->
|
||||
|
||||
{% block content %}
|
||||
{% autoescape on %}
|
||||
|
||||
<ul class="breadcrumb">
|
||||
<li><a href="{% url classic_recipe_search %}">OE-Classic</a> <span class="divider">→</span></li>
|
||||
<li class="active">{{ recipe.name }}</li>
|
||||
</ul>
|
||||
|
||||
<div class="container-fluid">
|
||||
|
||||
<div class="row-fluid">
|
||||
|
||||
<div class="page-header">
|
||||
<h1>{{ recipe.name }} {{ recipe.pv }}</h1>
|
||||
</div>
|
||||
|
||||
<div class="alert alert-warning">
|
||||
<b>NOTE:</b> This recipe is for OE-Classic, the older monolithic version of OpenEmbedded which is no longer actively developed. See below for migration information. If no replacement is available in current OpenEmbedded layers, you may be able to <a href="http://www.openembedded.org/wiki/Migrating_metadata_to_OE-Core">migrate the recipe</a> yourself.
|
||||
</div>
|
||||
|
||||
<table class="table table-striped table-bordered">
|
||||
<tbody>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<td>{{ recipe.name }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Version</th>
|
||||
<td>{{ recipe.pv }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Summary</th>
|
||||
<td>{{ recipe.summary }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Description</th>
|
||||
<td>{{ recipe.description }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Section</th>
|
||||
<td>{{ recipe.section }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>License</th>
|
||||
<td>{{ recipe.license }}*</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Homepage</th>
|
||||
<td>{% if recipe.homepage %}<a href="{{ recipe.homepage }}">{{ recipe.homepage }}</a>{% endif %}</td>
|
||||
</tr>
|
||||
{% if recipe.bugtracker %}
|
||||
<tr>
|
||||
<th>Bug tracker</th>
|
||||
<td><a href="{{ recipe.bugtracker }}">{{ recipe.bugtracker }}</a></td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
<tr>
|
||||
<th>Recipe file</th>
|
||||
<td>
|
||||
{% if recipe.vcs_web_url %}
|
||||
<a href="{{ recipe.vcs_web_url }}">{{ recipe.full_path }}</a>
|
||||
{% else %}
|
||||
{{ recipe.full_path }}
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<p>* - in OE-Classic, some of the license values were not accurate. Please refer to current recipes (if available) for this information.</p>
|
||||
|
||||
<h2>Migration information</h2>
|
||||
|
||||
{% if can_edit %}
|
||||
<form id="migration_form" class="form-inline" method="post">
|
||||
{% csrf_token %}
|
||||
{% for hidden in form.hidden_fields %}
|
||||
{{ hidden }}
|
||||
{% endfor %}
|
||||
|
||||
Coverage {{ form.cover_status }} <span id="id_span_cover_opts">by {{ form.cover_pn }} in {{ form.cover_layerbranch }}</span>
|
||||
<label class="checkbox" id="id_label_cover_verified">
|
||||
{{ form.cover_verified }} verified
|
||||
</label>
|
||||
<p>
|
||||
<label>
|
||||
Comment
|
||||
{{ form.cover_comment }}
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
<label>
|
||||
Category
|
||||
{{ form.classic_category }}
|
||||
</p>
|
||||
</label>
|
||||
<input type="submit" value="Save" class='btn' />
|
||||
</form>
|
||||
{% else %}
|
||||
<table class="table table-striped table-bordered">
|
||||
<tbody>
|
||||
<tr>
|
||||
<th class="span2">Coverage</th>
|
||||
<td>{{ recipe.get_cover_desc }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Categories</th>
|
||||
<td>{{ recipe.classic_category }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
{% endif %}
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endautoescape %}
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block scripts %}
|
||||
<script>
|
||||
enable_value_field = function() {
|
||||
cover_status = $('#id_cover_status').val()
|
||||
if( cover_status == 'U' || cover_status == 'N' ) {
|
||||
$('#id_cover_pn').prop('disabled', true);
|
||||
$('#id_cover_layerbranch').prop('disabled', true);
|
||||
$('#id_cover_verified').prop('disabled', true);
|
||||
$('#id_label_cover_verified').addClass('muted');
|
||||
$('#id_span_cover_opts').addClass('muted');
|
||||
}
|
||||
else {
|
||||
$('#id_cover_pn').prop('disabled', false);
|
||||
$('#id_cover_layerbranch').prop('disabled', false);
|
||||
$('#id_cover_verified').prop('disabled', false);
|
||||
$('#id_label_cover_verified').removeClass('muted');
|
||||
$('#id_span_cover_opts').removeClass('muted');
|
||||
}
|
||||
}
|
||||
|
||||
$(document).ready(function() {
|
||||
$('#id_cover_status').change(enable_value_field)
|
||||
enable_value_field()
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
115
templates/layerindex/classicrecipes.html
Normal file
115
templates/layerindex/classicrecipes.html
Normal file
|
@ -0,0 +1,115 @@
|
|||
{% extends "layerindex/classic_base.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% comment %}
|
||||
|
||||
layerindex-web - OE-Classic recipe search page template
|
||||
|
||||
Copyright (C) 2013 Intel Corporation
|
||||
Licensed under the MIT license, see COPYING.MIT for details
|
||||
|
||||
{% endcomment %}
|
||||
|
||||
|
||||
<!--
|
||||
{% block title_append %} - OE-Classic recipes{% endblock %}
|
||||
-->
|
||||
|
||||
{% block navs %}
|
||||
{% autoescape on %}
|
||||
<li class="active"><a href="{% url classic_recipe_search %}">Recipes</a></li>
|
||||
<li><a href="{% url classic_recipe_stats %}">Stats</a></li>
|
||||
{% endautoescape %}
|
||||
{% endblock %}
|
||||
|
||||
{% block content_inner %}
|
||||
{% autoescape on %}
|
||||
|
||||
<div class="row-fluid">
|
||||
|
||||
<div class="span12">
|
||||
<h2>OE-Classic recipes</h2>
|
||||
|
||||
<div class="alert alert-warning">
|
||||
<b>NOTE:</b> This is the recipe search for OE-Classic, the older monolithic version of OpenEmbedded which is no longer actively developed. <a href="{% url recipe_search 'master' %}">Click here</a> to search current recipes.
|
||||
</div>
|
||||
|
||||
<div class="row-fluid">
|
||||
<form id="search-form" class="form-inline" method="GET">
|
||||
<table class="search-form-table">
|
||||
<tbody>
|
||||
{% for field in search_form.visible_fields %}
|
||||
<tr>
|
||||
<td>
|
||||
{{ field.label }}
|
||||
</td>
|
||||
<td>
|
||||
{{ field }}
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
<button class="btn" type="submit">Search</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
{% if recipe_list %}
|
||||
<table class="table table-striped table-bordered recipestable">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Recipe name</th>
|
||||
<th>Version</th>
|
||||
<th class="span7">Description</th>
|
||||
<th class="span5">Status</th>
|
||||
<th>Categories</th>
|
||||
{% if multi_classic_layers %}
|
||||
<th>Layer</th>
|
||||
{% endif %}
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
{% for recipe in recipe_list %}
|
||||
<tr {% if recipe.preferred_count > 0 %}class="muted"{% endif %}>
|
||||
<td><a href="{% url classic_recipe recipe.id %}">{{ recipe.name }}</a></td>
|
||||
<td>{{ recipe.pv }}</td>
|
||||
<td>{{ recipe.short_desc }}</td>
|
||||
<td>{{ recipe.get_cover_desc }}</td>
|
||||
<td>{{ recipe.classic_category }}</td>
|
||||
{% if multi_classic_layers %}
|
||||
<td><a href="{% url layer_item recipe.layerbranch.layer.name %}">{{ recipe.layerbranch.layer.name }}</a></td>
|
||||
{% endif %}
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
{% if is_paginated %}
|
||||
{% load pagination %}
|
||||
{% pagination page_obj %}
|
||||
{% endif %}
|
||||
{% else %}
|
||||
{% if searched %}
|
||||
<p>No matching OE-Classic recipes in database.</p>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
{% endautoescape %}
|
||||
|
||||
{% endblock %}
|
||||
|
||||
|
||||
{% block scripts %}
|
||||
<script>
|
||||
$(document).ready(function() {
|
||||
firstfield = $("#search-form input:text").first()
|
||||
if( ! firstfield.val() )
|
||||
firstfield.focus()
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
56
templates/layerindex/classicstats.html
Normal file
56
templates/layerindex/classicstats.html
Normal file
|
@ -0,0 +1,56 @@
|
|||
{% extends "layerindex/classic_base.html" %}
|
||||
{% load i18n %}
|
||||
{% load nvd3_tags %}
|
||||
{% load staticfiles %}
|
||||
|
||||
{% comment %}
|
||||
|
||||
layerindex-web - OE-Classic recipe migration stats template
|
||||
|
||||
Copyright (C) 2013 Intel Corporation
|
||||
Licensed under the MIT license, see COPYING.MIT for details
|
||||
|
||||
{% endcomment %}
|
||||
|
||||
|
||||
<!--
|
||||
{% block title_append %} - OE-Classic recipe statistics{% endblock %}
|
||||
-->
|
||||
|
||||
{% block navs %}
|
||||
{% autoescape on %}
|
||||
<li><a href="{% url classic_recipe_search %}">Recipes</a></li>
|
||||
<li class="active"><a href="{% url classic_recipe_stats %}">Stats</a></li>
|
||||
{% endautoescape %}
|
||||
{% endblock %}
|
||||
|
||||
|
||||
|
||||
{% block content_inner %}
|
||||
{% autoescape on %}
|
||||
|
||||
<div class="row-fluid">
|
||||
|
||||
<h2>OE-Classic statistics</h2>
|
||||
|
||||
<h3>Migration status</h3>
|
||||
{% include_container "chart_status" 400 600 %}
|
||||
|
||||
<h3>Unmigrated recipes by category</h3>
|
||||
{% include_container "chart_category" 400 600 %}
|
||||
|
||||
</div>
|
||||
|
||||
{% endautoescape %}
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block scripts %}
|
||||
|
||||
<link media="all" href="{% static "css/nv.d3.css" %}" type="text/css" rel="stylesheet" />
|
||||
<script src="{% static "js/d3.v2.js" %}" type="text/javascript"></script>
|
||||
<script src="{% static "js/nv.d3.js" %}" type="text/javascript"></script>
|
||||
|
||||
{% load_chart charttype_status chartdata_status "chart_status" %}
|
||||
{% load_chart charttype_category chartdata_category "chart_category" %}
|
||||
{% endblock %}
|
Loading…
Reference in New Issue
Block a user