diff --git a/HISTORY.rst b/HISTORY.rst
index 05e6e80..7427e20 100644
--- a/HISTORY.rst
+++ b/HISTORY.rst
@@ -1,12 +1,20 @@
History
=======
+Version 1.1.0 - 2015/10/17
+--------------------------
+
+* Better 'manager_frontend' structure, cleaning some minor things, close #34;
+* Added ``asset_tag``;
+* Dont show 'available systems' form if all systems allready exist, close #35;
+* Moved 'Recalbox manifest' stuff into ``project.recalbox_manifest``, removed manifest loading from settings in profit of loading from ``urls.py``;
+
Version 1.0.2 - 2015/10/16
--------------------------
* Fixed CRLF caracters when editing recalbox config, close #31;
* Fixed not packaged assets with production settings;
-* Refactored asset management to use persistent manifest registry (avoid to load JSON manifest file on every request) and move it to its own embedded app to ``project.assets_cartographer``;
+* Refactored asset management to use persistent manifest registry (avoid to load JSON manifest file on every request) and move it to its own embedded app to ``project.assets_cartographer``, close #33;
* Open Virtual gamepad link into a new window/tab;
* Fixed Dropzone assets, close #32;
* Moved Python requirements to ``pip-requirements`` directory and refactored them for backward compatible with Recalbox versions without ``psutil`` yet;
diff --git a/README.rst b/README.rst
index 19b13d6..e78aa5e 100644
--- a/README.rst
+++ b/README.rst
@@ -68,7 +68,7 @@ Before doing anything, ensure the Raspberry can access to the internet else conf
Go where you want to install the manager directory then type the following commands: ::
- wget -q -O - https://raw.githubusercontent.com/sveetch/recalbox-manager/master/deployment/install.sh | bash /dev/stdin --release=1.0.2
+ wget -q -O - https://raw.githubusercontent.com/sveetch/recalbox-manager/master/deployment/install.sh | bash /dev/stdin --release=1.1.0
This will download an install script and automatically execute it to proceed to install.
diff --git a/__init__.py b/__init__.py
index f86e5f9..ed47ede 100644
--- a/__init__.py
+++ b/__init__.py
@@ -1,4 +1,4 @@
"""
Django project to manage a Recalbox from a web interface
"""
-__version__ = '1.0.2'
\ No newline at end of file
+__version__ = '1.1.0'
\ No newline at end of file
diff --git a/project/assets_cartographer/parser.py b/project/assets_cartographer/parser.py
index 58091d1..e163d46 100644
--- a/project/assets_cartographer/parser.py
+++ b/project/assets_cartographer/parser.py
@@ -27,7 +27,7 @@
This would eventually not work with static files through S3/etc..
"""
-import json, os
+import os
from django.conf import settings
from django.template import Context
diff --git a/project/manager_frontend/templates/assets/javascript_tag.html b/project/assets_cartographer/templates/assets/javascript_tag.html
similarity index 100%
rename from project/manager_frontend/templates/assets/javascript_tag.html
rename to project/assets_cartographer/templates/assets/javascript_tag.html
diff --git a/project/manager_frontend/templates/assets/stylesheet_tag.html b/project/assets_cartographer/templates/assets/stylesheet_tag.html
similarity index 100%
rename from project/manager_frontend/templates/assets/stylesheet_tag.html
rename to project/assets_cartographer/templates/assets/stylesheet_tag.html
diff --git a/project/assets_cartographer/templatetags/assets.py b/project/assets_cartographer/templatetags/assets.py
index 1ba2b8e..772f338 100644
--- a/project/assets_cartographer/templatetags/assets.py
+++ b/project/assets_cartographer/templatetags/assets.py
@@ -3,9 +3,10 @@
TODO: another tag for loading asset kind that are not stylesheet or javascript
"""
+from django.template.defaulttags import register
+
from project.assets_cartographer import manifest
from project.assets_cartographer.parser import AssetTagsManagerFromManifest
-from django.template.defaulttags import register
def render_asset_tags(kind, *bundle_names):
"""
@@ -18,6 +19,25 @@ def render_asset_tags(kind, *bundle_names):
return AssetTagsManagerFromManifest(manifest.get_registry()).render_for_kind(bundle_names, kind)
+@register.simple_tag
+def asset_tag(kind, *bundle_names):
+ """
+ Build tag for any kind of asset
+
+ Usage
+ *****
+
+ Give asset kind name as first argument, then package names (one or more) as other arguments: ::
+
+ {% asset_tag "stylesheet" "css/item1.min.css" "css/item2.min.css" .. %}
+
+ Depending on settings.ASSETS_PACKAGED it would build an unique tag (if True) for the packaged asset file or tag (if False) for each package components files.
+
+ Obviously, the given kind have to exist in your asset manifest.
+ """
+ return render_asset_tags(kind, *bundle_names)
+
+
@register.simple_tag
def stylesheet_tag(*bundle_names):
"""
@@ -26,7 +46,7 @@ def stylesheet_tag(*bundle_names):
Usage
*****
- Just gives package names (one or more) as arguments: ::
+ Just give package names (one or more) as arguments: ::
{% stylesheet_tag "css/item1.min.css" "css/item2.min.css" .. %}
@@ -43,7 +63,7 @@ def javascript_tag(*bundle_names):
Usage
*****
- Just gives package names (one or more) as arguments: ::
+ Just give package names (one or more) as arguments: ::
{% javascript_tag "css/item1.min.js" "css/item2.min.js" .. %}
diff --git a/project/manager_frontend/forms/systems.py b/project/manager_frontend/forms/systems.py
index e1ca1ff..bcc89f3 100644
--- a/project/manager_frontend/forms/systems.py
+++ b/project/manager_frontend/forms/systems.py
@@ -9,10 +9,14 @@
from django.utils.translation import ugettext_lazy as _
from django.core.files.storage import FileSystemStorage
+from project.recalbox_manifest import manifest as RECALBOX_MANIFEST
+
from project.manager_frontend.forms import CrispyFormMixin
+
from project.utils.imports import safe_import_module
-class SystemCreateForm(CrispyFormMixin, forms.Form):
+#class SystemCreateForm(CrispyFormMixin, forms.Form):
+class SystemCreateForm(forms.Form):
"""
Create a new system directory
"""
@@ -23,7 +27,7 @@ def __init__(self, *args, **kwargs):
self.available_systems = kwargs.pop('available_systems')
super(SystemCreateForm, self).__init__(*args, **kwargs)
- super(forms.Form, self).__init__(*args, **kwargs)
+ #super(forms.Form, self).__init__(*args, **kwargs)
self.fields['name'] = forms.ChoiceField(label=_('Add a new system'), choices=self.available_systems, required=True)
@@ -32,4 +36,4 @@ def save(self):
os.mkdir(os.path.join(settings.RECALBOX_ROMS_PATH, name), 0755)
- return settings.RECALBOX_MANIFEST[name]
+ return RECALBOX_MANIFEST[name]
diff --git a/project/manager_frontend/templates/manager_frontend/systems_list.html b/project/manager_frontend/templates/manager_frontend/systems_list.html
index 1267a39..aa00b92 100644
--- a/project/manager_frontend/templates/manager_frontend/systems_list.html
+++ b/project/manager_frontend/templates/manager_frontend/systems_list.html
@@ -7,15 +7,7 @@
{% trans "Total" %}: {{ systems_list|length }}
-{% comment %}{% for key,name in systems_list %}
-
-{% endfor %}
{% endcomment %}
-
-
+{% endif %}
{% for key,name in systems_list %}
diff --git a/project/utils/manifest.py b/project/manager_frontend/utils/manifest.py
similarity index 97%
rename from project/utils/manifest.py
rename to project/manager_frontend/utils/manifest.py
index 81ff255..bedccca 100644
--- a/project/utils/manifest.py
+++ b/project/manager_frontend/utils/manifest.py
@@ -5,7 +5,7 @@
import json
import xml.etree.cElementTree as ET
-class ManifestParser(object):
+class RecalboxManifestParser(object):
"""
Recalbox XML manifest parser
"""
diff --git a/project/utils/views.py b/project/manager_frontend/utils/views.py
similarity index 94%
rename from project/utils/views.py
rename to project/manager_frontend/utils/views.py
index 7515f74..9c90f5b 100644
--- a/project/utils/views.py
+++ b/project/manager_frontend/utils/views.py
@@ -1,15 +1,11 @@
"""
Some common class based views
-
-TODO: Move this into 'manager_frontend' app
"""
import json
from django.views.generic.edit import TemplateResponseMixin, FormMixin, ProcessFormView
from django.http import HttpResponse
-from project.manager_frontend.forms.roms import RomUploadForm, RomDeleteForm
-
class MultiFormView(TemplateResponseMixin, FormMixin, ProcessFormView):
"""
This is a huge rewrite and mixing of some CBV views and mixins to be able
@@ -29,9 +25,9 @@ class MultiFormView(TemplateResponseMixin, FormMixin, ProcessFormView):
Both must be unique through the forms of the view.
- WARNING: This view should implement some warnings about needed stuff like
- form attributes and form object names if not defined. So actually
- you'll have to remember of this yourself.
+ TODO: This view should implement some warnings about needed stuff like
+ form attributes and form object names if not defined. So actually
+ you'll have to remember of this yourself.
"""
# The patterns used to find method/names from form 'form_key' attribute
getformkwargs_pattern = 'get_{}_form_kwargs'
diff --git a/project/manager_frontend/views/bios.py b/project/manager_frontend/views/bios.py
index 6958aef..93e5566 100644
--- a/project/manager_frontend/views/bios.py
+++ b/project/manager_frontend/views/bios.py
@@ -1,19 +1,19 @@
"""
Views for Bios views
"""
-import os, re
+import os
from operator import itemgetter
from django.conf import settings
-from django.views.generic import TemplateView
-from django.views.generic.edit import FormView
from django.core.urlresolvers import reverse
from django.contrib import messages
from django.http import Http404, HttpResponseBadRequest
from django.utils.translation import ugettext as _
+from project.recalbox_manifest import manifest as RECALBOX_MANIFEST
+
from project.manager_frontend.forms.bios import BiosDeleteForm, BiosUploadForm
-from project.utils.views import MultiFormView, JsonMixin
+from project.manager_frontend.utils.views import MultiFormView, JsonMixin
class BiosListView(MultiFormView):
"""
@@ -49,7 +49,7 @@ def get_bios_manifest(self):
"""
bios_dict = {}
- for system_key, system_datas in settings.RECALBOX_MANIFEST.items():
+ for system_key, system_datas in RECALBOX_MANIFEST.items():
system_name = system_datas.get('name', system_key)
if len(system_datas.get('bios', []))>0:
for md5hash,filename in system_datas['bios']:
diff --git a/project/manager_frontend/views/config.py b/project/manager_frontend/views/config.py
index acc632b..c4fe7cd 100644
--- a/project/manager_frontend/views/config.py
+++ b/project/manager_frontend/views/config.py
@@ -4,8 +4,6 @@
import os
from django.conf import settings
-from django.shortcuts import render
-from django.views.generic import TemplateView
from django.views.generic.edit import FormView
from django.core.urlresolvers import reverse
from django.contrib import messages
diff --git a/project/manager_frontend/views/logs.py b/project/manager_frontend/views/logs.py
index 384b23b..0fead4b 100644
--- a/project/manager_frontend/views/logs.py
+++ b/project/manager_frontend/views/logs.py
@@ -2,7 +2,6 @@
Views for logs files
"""
from django.conf import settings
-from django.shortcuts import render
from django.views.generic import TemplateView
class LogsView(TemplateView):
diff --git a/project/manager_frontend/views/monitor.py b/project/manager_frontend/views/monitor.py
index e98c351..e3f6ea9 100644
--- a/project/manager_frontend/views/monitor.py
+++ b/project/manager_frontend/views/monitor.py
@@ -2,7 +2,6 @@
Monitoring views
"""
from django.conf import settings
-from django.shortcuts import render
from django.views.generic import TemplateView
# Compatibility support for Recalbox versions from 3.2.x to 3.3.x
diff --git a/project/manager_frontend/views/roms.py b/project/manager_frontend/views/roms.py
index 45e69c5..4b896ac 100644
--- a/project/manager_frontend/views/roms.py
+++ b/project/manager_frontend/views/roms.py
@@ -1,18 +1,19 @@
"""
Views for roms
"""
-import json, os
+import os
from operator import itemgetter
from django.conf import settings
-from django.views.generic import TemplateView
from django.core.urlresolvers import reverse
from django.contrib import messages
from django.http import Http404, HttpResponseBadRequest
from django.utils.translation import ugettext as _
+from project.recalbox_manifest import manifest as RECALBOX_MANIFEST
+
from project.manager_frontend.forms.roms import RomUploadForm, RomDeleteForm
-from project.utils.views import MultiFormView, JsonMixin
+from project.manager_frontend.utils.views import MultiFormView, JsonMixin
class RomListView(MultiFormView):
@@ -42,7 +43,7 @@ def init_system(self):
'name': self.system_key
})
# Get the system manifest part if any, else a default dict
- self.system_manifest = settings.RECALBOX_MANIFEST.get(self.system_key, default_manifest)
+ self.system_manifest = RECALBOX_MANIFEST.get(self.system_key, default_manifest)
def get_rom_choices(self):
rom_list = []
diff --git a/project/manager_frontend/views/systems.py b/project/manager_frontend/views/systems.py
index 44f0464..55e78ec 100644
--- a/project/manager_frontend/views/systems.py
+++ b/project/manager_frontend/views/systems.py
@@ -10,6 +10,8 @@
from django.contrib import messages
from django.utils.translation import ugettext as _
+from project.recalbox_manifest import manifest as RECALBOX_MANIFEST
+
from project.manager_frontend.forms.systems import SystemCreateForm
class SystemsListView(FormView):
@@ -29,14 +31,14 @@ def get_system_list(self):
# Only display directories
if os.path.isdir(os.path.join(path, item)) and not item.startswith('.'):
# Try to find the dirname in the system manifest
- if item in settings.RECALBOX_MANIFEST:
- existing_sys.append( (item, settings.RECALBOX_MANIFEST[item]['name']) )
+ if item in RECALBOX_MANIFEST:
+ existing_sys.append( (item, RECALBOX_MANIFEST[item]['name']) )
# Unknowed dirname
else:
existing_sys.append( (item, item) )
# Get system available: the ones that dont allready have a directory in systems dir
- for sys_key, sys_values in settings.RECALBOX_MANIFEST.items():
+ for sys_key, sys_values in RECALBOX_MANIFEST.items():
if sys_key not in [v[0] for v in existing_sys]:
available_sys.append( (sys_key, sys_values['name']) )
@@ -50,6 +52,7 @@ def get_context_data(self, **kwargs):
context.update({
'systems_path': settings.RECALBOX_ROMS_PATH,
'systems_list': self.existing_systems,
+ 'available_systems': self.available_systems,
})
return context
@@ -63,11 +66,8 @@ def get_form_kwargs(self):
def form_valid(self, form):
new_system = form.save()
- ## Throw a message to tell about upload success
- #if is_backuped:
- #messages.success(self.request, _('File has been backuped then edited'))
- #else:
- #messages.success(self.request, _('File has been edited'))
+ # Throw a message to tell about upload success
+ messages.success(self.request, _('System "{}" has been created').format(new_system['name']))
return super(SystemsListView, self).form_valid(form)
diff --git a/project/recalbox_manifest/__init__.py b/project/recalbox_manifest/__init__.py
new file mode 100644
index 0000000..79f80a7
--- /dev/null
+++ b/project/recalbox_manifest/__init__.py
@@ -0,0 +1,17 @@
+"""
+Recalbox manifest to know everything about supported systems
+"""
+import os, json
+from .registry import manifest
+
+def autodiscover():
+ """
+ Dummy loader actually, this should be backward compatible for futur assets
+ discovery per app
+ """
+ from django.conf import settings
+ from .parser import RecalboxManifestParser
+ # Fill the registry with the whole JSON manifest
+ manifest.update(
+ RecalboxManifestParser(settings.RECALBOX_MANIFEST_FILEPATH).read()
+ )
diff --git a/project/recalbox_manifest/parser.py b/project/recalbox_manifest/parser.py
new file mode 100644
index 0000000..acd0251
--- /dev/null
+++ b/project/recalbox_manifest/parser.py
@@ -0,0 +1,63 @@
+# -*- coding: utf-8 -*-
+"""
+Manifest parser
+"""
+import json
+import xml.etree.cElementTree as ET
+
+class RecalboxManifestParser(object):
+ """
+ Recalbox XML manifest parser
+ """
+ def __init__(self, filepath):
+ self.filepath = filepath
+
+ def get_system_extensions(self, node):
+ if node.find("extensions") is None:
+ return []
+ return [item.text for item in node.find("extensions").findall("extension")]
+
+ def get_system_download_links(self, node):
+ if node.find("download_links") is None:
+ return []
+ return [item.text for item in node.find("download_links").findall("link")]
+
+ def get_system_bios(self, node):
+ if node.find("bios") is None:
+ return []
+ return [(item.get('md5', ''), item.text) for item in node.find("bios").findall("file")]
+
+ def get_system_extra_comments(self, node):
+ if node.find("extra_comments") is None:
+ return []
+ return [item.text for item in node.find("extra_comments").findall("comment")]
+
+ def read(self):
+ """
+ Return the manifest as a Python dictionnary
+ """
+ manifest = {}
+
+ print "self.filepath:", self.filepath
+
+ tree = ET.parse(self.filepath)
+
+ root = tree.getroot()
+
+ for node in root:
+ system_key = node.get('key')
+ manifest[system_key] = {
+ 'name': node.get('name'),
+ 'extensions': self.get_system_extensions(node),
+ 'download_links': self.get_system_download_links(node),
+ 'bios': self.get_system_bios(node),
+ 'extra_comments': self.get_system_extra_comments(node),
+ }
+
+ return manifest
+
+ def json(self):
+ """
+ Return the manifest as a JSON string
+ """
+ return json.dumps(self.read(), indent=4)
diff --git a/project/recalbox_manifest/registry.py b/project/recalbox_manifest/registry.py
new file mode 100644
index 0000000..b1fb9a3
--- /dev/null
+++ b/project/recalbox_manifest/registry.py
@@ -0,0 +1,6 @@
+# -*- coding: utf-8 -*-
+"""
+Recalbox manifest registry
+"""
+# Just a dummy dict waiting to be filled
+manifest = {}
diff --git a/project/settings.py b/project/settings.py
index 293f794..97f9b54 100644
--- a/project/settings.py
+++ b/project/settings.py
@@ -13,7 +13,7 @@
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
import os
-from utils.manifest import ManifestParser
+from manager_frontend.utils.manifest import RecalboxManifestParser
gettext = lambda s: s
@@ -55,6 +55,7 @@
'autobreadcrumbs',
'project.assets_cartographer',
+ 'project.recalbox_manifest',
'project.manager_frontend',
#'ajaxuploader',
@@ -151,6 +152,9 @@
os.path.join(PROJECT_DIR, "webapp_statics"),
)
+# For Django messages framework to avoid database requests
+MESSAGE_STORAGE = 'django.contrib.messages.storage.cookie.CookieStorage'
+
# For assets management
ASSETS_PACKAGED = not(DEBUG)
ASSETS_STRICT = False
@@ -159,8 +163,6 @@
"javascripts": "assets/javascript_tag.html",
"stylesheets": "assets/stylesheet_tag.html",
}
-# For Django messages framework, we could use this settings to avoid database requests
-#MESSAGE_STORAGE = 'django.contrib.messages.storage.cookie.CookieStorage'
#
# Recalbox needed paths
@@ -187,8 +189,5 @@
'bios': [],
}
-# Recalbox manifest about system roms and bios
-RECALBOX_MANIFEST = ManifestParser(RECALBOX_MANIFEST_FILEPATH).read()
-
# Blocking time during psutil watch for cpu charge in second (float number)
RECALBOX_PSUTIL_CPU_INTERVAL = 0.5 # 0.1 seems a little too low but 1.0 add 1s on page loading time
diff --git a/project/urls.py b/project/urls.py
index 017c5cc..7536b29 100644
--- a/project/urls.py
+++ b/project/urls.py
@@ -18,12 +18,18 @@
from django.contrib import admin
from django.views.generic import TemplateView
+# Discover crumbs from enabled apps
import autobreadcrumbs
autobreadcrumbs.autodiscover()
+# Load asset manifest in memory
from project import assets_cartographer
assets_cartographer.autodiscover()
+# Load Recalbox manifest in memory
+from project import recalbox_manifest
+recalbox_manifest.autodiscover()
+
urlpatterns = [
url(r'^admin/', include(admin.site.urls)),
url(r'^', include('project.manager_frontend.urls', namespace='manager')),