diff --git a/db_apps/oemof_results.py b/db_apps/oemof_results.py index 606c932..0782660 100644 --- a/db_apps/oemof_results.py +++ b/db_apps/oemof_results.py @@ -1,9 +1,7 @@ - import re import pandas import transaction -from sqlalchemy import ( - Column, Integer, String, ARRAY, ForeignKey) +from sqlalchemy import Column, Integer, String, ARRAY, ForeignKey from sqlalchemy.orm import relationship from sqlalchemy.dialects.postgresql import DOUBLE_PRECISION from sqlalchemy.ext.declarative import declarative_base @@ -48,90 +46,49 @@ class OemofInputResult(Base): - __tablename__ = 'stemp_oemof_input_result' + __tablename__ = "stemp_oemof_input_result" - input_result_id = Column( - Integer, - primary_key=True - ) - input_id = Column(Integer, ForeignKey('stemp_oemof_data.data_id')) - result_id = Column(Integer, ForeignKey('stemp_oemof_data.data_id')) + input_result_id = Column(Integer, primary_key=True) + input_id = Column(Integer, ForeignKey("stemp_oemof_data.data_id")) + result_id = Column(Integer, ForeignKey("stemp_oemof_data.data_id")) input = relationship( - "OemofData", - backref="input", - uselist=False, - foreign_keys=[input_id], + "OemofData", backref="input", uselist=False, foreign_keys=[input_id], ) result = relationship( - "OemofData", - backref="result", - uselist=False, - foreign_keys=[result_id], + "OemofData", backref="result", uselist=False, foreign_keys=[result_id], ) class OemofData(Base): - __tablename__ = 'stemp_oemof_data' + __tablename__ = "stemp_oemof_data" - data_id = Column( - Integer, - primary_key=True - ) + data_id = Column(Integer, primary_key=True) scalars = relationship("OemofScalar", cascade="delete") sequences = relationship("OemofSequence", cascade="delete") class OemofScalar(Base): - __tablename__ = 'stemp_oemof_scalar' + __tablename__ = "stemp_oemof_scalar" - scalar_id = Column( - Integer, - primary_key=True - ) - data_id = Column( - Integer, ForeignKey('stemp_oemof_data.data_id')) - from_node = Column( - String - ) - to_node = Column( - String - ) - attribute = Column( - String - ) - value = Column( - String - ) - type = Column( - String - ) + scalar_id = Column(Integer, primary_key=True) + data_id = Column(Integer, ForeignKey("stemp_oemof_data.data_id")) + from_node = Column(String) + to_node = Column(String) + attribute = Column(String) + value = Column(String) + type = Column(String) class OemofSequence(Base): - __tablename__ = 'stemp_oemof_sequence' + __tablename__ = "stemp_oemof_sequence" - sequence_id = Column( - Integer, - primary_key=True - ) - data_id = Column( - Integer, ForeignKey('stemp_oemof_data.data_id')) - from_node = Column( - String - ) - to_node = Column( - String, - nullable=True - ) - attribute = Column( - String - ) - value = Column( - ARRAY(DOUBLE_PRECISION) - ) - type = Column( - String - ) + sequence_id = Column(Integer, primary_key=True) + data_id = Column(Integer, ForeignKey("stemp_oemof_data.data_id")) + from_node = Column(String) + to_node = Column(String, nullable=True) + attribute = Column(String) + value = Column(ARRAY(DOUBLE_PRECISION)) + type = Column(String) def store_results(session, input_data, result_data): @@ -166,34 +123,33 @@ def store_results(session, input_data, result_data): result_data = convert_keys_to_strings(result_data) input_result = OemofInputResult() - for input_result_attr, data in ( - ('input', input_data), ('result', result_data)): + for input_result_attr, data in (("input", input_data), ("result", result_data)): scalars = [] sequences = [] for (from_node, to_node), sc_sq_dict in data.items(): - for key, value in sc_sq_dict['scalars'].items(): + for key, value in sc_sq_dict["scalars"].items(): scalars.append( OemofScalar( from_node=from_node, to_node=to_node, attribute=key, value=value, - type=type(value).__name__ + type=type(value).__name__, ) ) session.add_all(scalars) - for key, series in sc_sq_dict['sequences'].items(): - list_type = 'list' + for key, series in sc_sq_dict["sequences"].items(): + list_type = "list" if isinstance(series, pandas.Series): series = series.values.tolist() - list_type = 'series' + list_type = "series" sequences.append( OemofSequence( from_node=from_node, to_node=to_node, attribute=key, value=series, - type=list_type + type=list_type, ) ) session.add_all(sequences) @@ -208,8 +164,9 @@ def store_results(session, input_data, result_data): return result_id -def restore_results(session, input_result_id, restore_none_type=False, - advanced_label=None): +def restore_results( + session, input_result_id, restore_none_type=False, advanced_label=None +): """ Restores input and result data from OemofInputResult from DB @@ -229,49 +186,47 @@ def restore_results(session, input_result_id, restore_none_type=False, (dict, dict): Restored input- and result-data """ + def type_conversion(value_str, value_type): - if value_type == 'str': + if value_type == "str": return value_str - elif value_type == 'float': + elif value_type == "float": return float(value_str) - elif value_type == 'int': + elif value_type == "int": return int(value_str) - elif value_type == 'bool': + elif value_type == "bool": return bool(value_str) else: raise TypeError('Unknown conversion type "' + value_type + '"') def convert_label_to_namedtuple(label): def unpack_tuples(value): - if value.startswith('(') and value.endswith(')'): - value_list = value[1:-1].split(', ') + if value.startswith("(") and value.endswith(")"): + value_list = value[1:-1].split(", ") if len(value_list) == 1: single = value_list[0][:-1] - return (None, ) if single == 'None' else (single, ) + return (None,) if single == "None" else (single,) else: - return (None if v == 'None' else v for v in value_list) + return (None if v == "None" else v for v in value_list) else: - return None if value == 'None' else value + return None if value == "None" else value pattern = ( - f"{advanced_label.__name__}\(" + - ', '.join( + f"{advanced_label.__name__}\(" + + ", ".join( [ f"{field}=(?P<{field}>[\(\)A-Za-z0-9#._\-,\ ']*)" for field in advanced_label._fields ] - ) + - "\)" + ) + + "\)" ) try: - match = re.match(pattern, label.replace('\'', '')) + match = re.match(pattern, label.replace("'", "")) except AttributeError: return label if match is not None: - fields = { - k: unpack_tuples(v) - for k, v in match.groupdict().items() - } + fields = {k: unpack_tuples(v) for k, v in match.groupdict().items()} return advanced_label(**fields) else: return label @@ -281,35 +236,39 @@ def get_nodes(scalar_or_sequence): if advanced_label is not None: raw_nodes = tuple(map(convert_label_to_namedtuple, raw_nodes)) if restore_none_type: - return tuple(map(lambda x: None if x == 'None' else x, raw_nodes)) + return tuple(map(lambda x: None if x == "None" else x, raw_nodes)) else: return raw_nodes # Find results: - input_result = session.query(OemofInputResult).filter( - OemofInputResult.input_result_id == input_result_id).first() + input_result = ( + session.query(OemofInputResult) + .filter(OemofInputResult.input_result_id == input_result_id) + .first() + ) if input_result is None: raise IndexError( - 'Could not find OemofInputResult with ID #' + str(input_result_id)) + "Could not find OemofInputResult with ID #" + str(input_result_id) + ) input_data = {} result_data = {} - for input_result_attr, data in ( - ('input', input_data), ('result', result_data)): + for input_result_attr, data in (("input", input_data), ("result", result_data)): ir_attr = getattr(input_result, input_result_attr) for scalar in ir_attr.scalars: nodes = get_nodes(scalar) if nodes not in data: - data[nodes] = {'scalars': {}, 'sequences': {}} - data[nodes]['scalars'][scalar.attribute] = type_conversion( - scalar.value, scalar.type) + data[nodes] = {"scalars": {}, "sequences": {}} + data[nodes]["scalars"][scalar.attribute] = type_conversion( + scalar.value, scalar.type + ) for sequence in ir_attr.sequences: nodes = get_nodes(sequence) if nodes not in data: - data[nodes] = {'scalars': {}, 'sequences': {}} - if sequence.type == 'series': + data[nodes] = {"scalars": {}, "sequences": {}} + if sequence.type == "series": series = pandas.Series(sequence.value) else: series = sequence.value - data[nodes]['sequences'][sequence.attribute] = series + data[nodes]["sequences"][sequence.attribute] = series return input_data, result_data diff --git a/doc/_static/run_tasks.py b/doc/_static/run_tasks.py index 3b590f0..1bf1165 100644 --- a/doc/_static/run_tasks.py +++ b/doc/_static/run_tasks.py @@ -1,4 +1,3 @@ - from app_name import tasks diff --git a/doc/_static/tasks.py b/doc/_static/tasks.py index 8092883..8b6d4dc 100644 --- a/doc/_static/tasks.py +++ b/doc/_static/tasks.py @@ -1,4 +1,3 @@ - from wam.celery import app diff --git a/doc/conf.py b/doc/conf.py index b9ea50f..54175ac 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -19,14 +19,14 @@ # -- Project information ----------------------------------------------------- -project = 'WAM-Server' -copyright = '2018, Hendrik Huyskens' -author = 'Hendrik Huyskens' +project = "WAM-Server" +copyright = "2018, Hendrik Huyskens" +author = "Hendrik Huyskens" # The short X.Y version -version = '' +version = "" # The full version, including alpha/beta/rc tags -release = '' +release = "" # -- General configuration --------------------------------------------------- @@ -39,25 +39,25 @@ # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. extensions = [ - 'sphinx.ext.autodoc', - 'sphinx.ext.doctest', - 'sphinx.ext.intersphinx', - 'sphinx.ext.todo', - 'sphinx.ext.coverage', - 'sphinx.ext.viewcode', + "sphinx.ext.autodoc", + "sphinx.ext.doctest", + "sphinx.ext.intersphinx", + "sphinx.ext.todo", + "sphinx.ext.coverage", + "sphinx.ext.viewcode", ] # Add any paths that contain templates here, relative to this directory. -templates_path = ['_templates'] +templates_path = ["_templates"] # The suffix(es) of source filenames. # You can specify multiple suffix as a list of string: # # source_suffix = ['.rst', '.md'] -source_suffix = '.rst' +source_suffix = ".rst" # The master toctree document. -master_doc = 'index' +master_doc = "index" # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. @@ -80,7 +80,7 @@ # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. # -html_theme = 'alabaster' +html_theme = "alabaster" # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the @@ -91,7 +91,7 @@ # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". -html_static_path = ['_static'] +html_static_path = ["_static"] # Custom sidebar templates, must be a dictionary that maps document names # to template names. @@ -107,7 +107,7 @@ # -- Options for HTMLHelp output --------------------------------------------- # Output file base name for HTML help builder. -htmlhelp_basename = 'WAM-Serverdoc' +htmlhelp_basename = "WAM-Serverdoc" # -- Options for LaTeX output ------------------------------------------------ @@ -116,15 +116,12 @@ # The paper size ('letterpaper' or 'a4paper'). # # 'papersize': 'letterpaper', - # The font size ('10pt', '11pt' or '12pt'). # # 'pointsize': '10pt', - # Additional stuff for the LaTeX preamble. # # 'preamble': '', - # Latex figure (float) alignment # # 'figure_align': 'htbp', @@ -134,8 +131,13 @@ # (source start file, target name, title, # author, documentclass [howto, manual, or own class]). latex_documents = [ - (master_doc, 'WAM-Server.tex', 'WAM-Server Documentation', - 'Hendrik Huyskens', 'manual'), + ( + master_doc, + "WAM-Server.tex", + "WAM-Server Documentation", + "Hendrik Huyskens", + "manual", + ), ] @@ -143,10 +145,7 @@ # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). -man_pages = [ - (master_doc, 'wam-server', 'WAM-Server Documentation', - [author], 1) -] +man_pages = [(master_doc, "wam-server", "WAM-Server Documentation", [author], 1)] # -- Options for Texinfo output ---------------------------------------------- @@ -155,9 +154,15 @@ # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ - (master_doc, 'WAM-Server', 'WAM-Server Documentation', - author, 'WAM-Server', 'One line description of project.', - 'Miscellaneous'), + ( + master_doc, + "WAM-Server", + "WAM-Server Documentation", + author, + "WAM-Server", + "One line description of project.", + "Miscellaneous", + ), ] @@ -176,7 +181,7 @@ # epub_uid = '' # A list of files that should not be packed into the epub file. -epub_exclude_files = ['search.html'] +epub_exclude_files = ["search.html"] # -- Extension configuration ------------------------------------------------- @@ -184,9 +189,9 @@ # -- Options for intersphinx extension --------------------------------------- # Example configuration for intersphinx: refer to the Python standard library. -intersphinx_mapping = {'https://docs.python.org/': None} +intersphinx_mapping = {"https://docs.python.org/": None} # -- Options for todo extension ---------------------------------------------- # If true, `todo` and `todoList` produce output, else they produce nothing. -todo_include_todos = True \ No newline at end of file +todo_include_todos = True diff --git a/install_requirements.py b/install_requirements.py index 2854758..f2d00b4 100644 --- a/install_requirements.py +++ b/install_requirements.py @@ -2,11 +2,9 @@ import subprocess base_dir = os.path.abspath(os.path.curdir) -pip_path = subprocess.check_output(['which', 'pip']).decode().strip('\n') -apps = os.environ['WAM_APPS'].split(',') +pip_path = subprocess.check_output(["which", "pip"]).decode().strip("\n") +apps = os.environ["WAM_APPS"].split(",") for app in apps: - req_file = os.path.join(base_dir, app, 'requirements.txt') + req_file = os.path.join(base_dir, app, "requirements.txt") if os.path.isfile(req_file): - subprocess.call( - [pip_path, 'install', '-r', req_file] - ) + subprocess.call([pip_path, "install", "-r", req_file]) diff --git a/meta/admin.py b/meta/admin.py index 457fab9..6527fa7 100644 --- a/meta/admin.py +++ b/meta/admin.py @@ -1,4 +1,3 @@ - from wam.admin import wam_admin_site from meta import models diff --git a/meta/apps.py b/meta/apps.py index 13f6832..1767574 100644 --- a/meta/apps.py +++ b/meta/apps.py @@ -2,4 +2,4 @@ class MetaConfig(AppConfig): - name = 'meta' + name = "meta" diff --git a/meta/migrations/0001_initial.py b/meta/migrations/0001_initial.py index 1849d75..c53b444 100644 --- a/meta/migrations/0001_initial.py +++ b/meta/migrations/0001_initial.py @@ -8,49 +8,76 @@ class Migration(migrations.Migration): initial = True - dependencies = [ - ] + dependencies = [] operations = [ migrations.CreateModel( - name='Assumption', + name="Assumption", fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('name', models.CharField(max_length=255)), - ('description', models.TextField()), - ('value', models.CharField(max_length=64)), - ('value_type', models.CharField(max_length=32)), - ('app_name', models.CharField(max_length=255)), + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("name", models.CharField(max_length=255)), + ("description", models.TextField()), + ("value", models.CharField(max_length=64)), + ("value_type", models.CharField(max_length=32)), + ("app_name", models.CharField(max_length=255)), ], ), migrations.CreateModel( - name='Source', + name="Source", fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('author', models.CharField(max_length=255)), - ('url', models.URLField()), - ('description', models.TextField()), - ('year', models.IntegerField()), - ('license', models.CharField(max_length=255)), - ('app_name', models.CharField(max_length=255)), + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("author", models.CharField(max_length=255)), + ("url", models.URLField()), + ("description", models.TextField()), + ("year", models.IntegerField()), + ("license", models.CharField(max_length=255)), + ("app_name", models.CharField(max_length=255)), ], ), migrations.CreateModel( - name='SourceCategory', + name="SourceCategory", fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('name', models.CharField(max_length=255)), - ('description', models.TextField()), + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("name", models.CharField(max_length=255)), + ("description", models.TextField()), ], ), migrations.AddField( - model_name='source', - name='category', - field=models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='meta.SourceCategory'), + model_name="source", + name="category", + field=models.ForeignKey( + on_delete=django.db.models.deletion.DO_NOTHING, to="meta.SourceCategory" + ), ), migrations.AddField( - model_name='assumption', - name='source', - field=models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='meta.Source'), + model_name="assumption", + name="source", + field=models.ForeignKey( + on_delete=django.db.models.deletion.DO_NOTHING, to="meta.Source" + ), ), ] diff --git a/meta/migrations/0002_auto_20181130_1141.py b/meta/migrations/0002_auto_20181130_1141.py index 67e61bd..251c35b 100644 --- a/meta/migrations/0002_auto_20181130_1141.py +++ b/meta/migrations/0002_auto_20181130_1141.py @@ -6,13 +6,11 @@ class Migration(migrations.Migration): dependencies = [ - ('meta', '0001_initial'), + ("meta", "0001_initial"), ] operations = [ migrations.RenameField( - model_name='assumption', - old_name='value_type', - new_name='unit', + model_name="assumption", old_name="value_type", new_name="unit", ), ] diff --git a/meta/migrations/0003_source_meta_data.py b/meta/migrations/0003_source_meta_data.py index 1c9c5b2..d392e9b 100644 --- a/meta/migrations/0003_source_meta_data.py +++ b/meta/migrations/0003_source_meta_data.py @@ -7,13 +7,13 @@ class Migration(migrations.Migration): dependencies = [ - ('meta', '0002_auto_20181130_1141'), + ("meta", "0002_auto_20181130_1141"), ] operations = [ migrations.AddField( - model_name='source', - name='meta_data', + model_name="source", + name="meta_data", field=django.contrib.postgres.fields.jsonb.JSONField(null=True), ), ] diff --git a/meta/migrations/0004_auto_20190207_1139.py b/meta/migrations/0004_auto_20190207_1139.py index 354a78a..7d048bc 100644 --- a/meta/migrations/0004_auto_20190207_1139.py +++ b/meta/migrations/0004_auto_20190207_1139.py @@ -7,18 +7,19 @@ class Migration(migrations.Migration): dependencies = [ - ('meta', '0003_source_meta_data'), + ("meta", "0003_source_meta_data"), ] operations = [ - migrations.RenameModel( - old_name='SourceCategory', - new_name='Category', - ), + migrations.RenameModel(old_name="SourceCategory", new_name="Category",), migrations.AddField( - model_name='assumption', - name='category', - field=models.ForeignKey(default=None, on_delete=django.db.models.deletion.DO_NOTHING, to='meta.Category'), + model_name="assumption", + name="category", + field=models.ForeignKey( + default=None, + on_delete=django.db.models.deletion.DO_NOTHING, + to="meta.Category", + ), preserve_default=False, ), ] diff --git a/meta/migrations/0005_auto_20190211_1125.py b/meta/migrations/0005_auto_20190211_1125.py index 88ba2e8..128a367 100644 --- a/meta/migrations/0005_auto_20190211_1125.py +++ b/meta/migrations/0005_auto_20190211_1125.py @@ -6,28 +6,13 @@ class Migration(migrations.Migration): dependencies = [ - ('meta', '0004_auto_20190207_1139'), + ("meta", "0004_auto_20190207_1139"), ] operations = [ - migrations.RemoveField( - model_name='source', - name='author', - ), - migrations.RemoveField( - model_name='source', - name='description', - ), - migrations.RemoveField( - model_name='source', - name='license', - ), - migrations.RemoveField( - model_name='source', - name='url', - ), - migrations.RemoveField( - model_name='source', - name='year', - ), + migrations.RemoveField(model_name="source", name="author",), + migrations.RemoveField(model_name="source", name="description",), + migrations.RemoveField(model_name="source", name="license",), + migrations.RemoveField(model_name="source", name="url",), + migrations.RemoveField(model_name="source", name="year",), ] diff --git a/meta/migrations/0006_auto_20191212_1617.py b/meta/migrations/0006_auto_20191212_1617.py index 9c221e5..9a79ab7 100644 --- a/meta/migrations/0006_auto_20191212_1617.py +++ b/meta/migrations/0006_auto_20191212_1617.py @@ -7,23 +7,29 @@ class Migration(migrations.Migration): dependencies = [ - ('meta', '0005_auto_20190211_1125'), + ("meta", "0005_auto_20190211_1125"), ] operations = [ migrations.AlterField( - model_name='assumption', - name='category', - field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='meta.Category'), + model_name="assumption", + name="category", + field=models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, to="meta.Category" + ), ), migrations.AlterField( - model_name='assumption', - name='source', - field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='meta.Source'), + model_name="assumption", + name="source", + field=models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, to="meta.Source" + ), ), migrations.AlterField( - model_name='source', - name='category', - field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='meta.Category'), + model_name="source", + name="category", + field=models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, to="meta.Category" + ), ), ] diff --git a/meta/models.py b/meta/models.py index 41c1188..283b865 100644 --- a/meta/models.py +++ b/meta/models.py @@ -1,4 +1,3 @@ - import jmespath from django.db import models @@ -13,6 +12,7 @@ class Assumption(models.Model): A source must be assigned to exactly one :class:`Source` defined in `source`. """ + name = models.CharField(max_length=255) description = models.TextField() value = models.CharField(max_length=64) @@ -20,8 +20,8 @@ class Assumption(models.Model): app_name = models.CharField(max_length=255) - source = models.ForeignKey('Source', on_delete=models.CASCADE) - category = models.ForeignKey('Category', on_delete=models.CASCADE) + source = models.ForeignKey("Source", on_delete=models.CASCADE) + category = models.ForeignKey("Category", on_delete=models.CASCADE) def __str__(self): return self.name @@ -33,30 +33,31 @@ class Source(models.Model): A source must be assigned to exactly one :class:`Category` defined in `category`. """ + meta_data = JSONField(null=True) app_name = models.CharField(max_length=255) - category = models.ForeignKey('Category', on_delete=models.CASCADE) + category = models.ForeignKey("Category", on_delete=models.CASCADE) def json(self): return JsonWidget(self.meta_data).render() def infos(self): infos = jmespath.search( - '{' - 'url: identifier, ' - 'title: title, ' - 'description: description, ' - 'licenses: licenses[*].name' - '}', - self.meta_data + "{" + "url: identifier, " + "title: title, " + "description: description, " + "licenses: licenses[*].name" + "}", + self.meta_data, ) - if infos.get('url') is not None: - if infos.get('url') == '': - infos.pop('url') + if infos.get("url") is not None: + if infos.get("url") == "": + infos.pop("url") else: - infos.pop('url') + infos.pop("url") return infos @@ -65,6 +66,7 @@ class Category(models.Model): A category subsumes one or multiple sources, see :class:`Source`. """ + name = models.CharField(max_length=255) description = models.TextField() diff --git a/meta/views.py b/meta/views.py index de52f23..61ff53c 100644 --- a/meta/views.py +++ b/meta/views.py @@ -36,56 +36,52 @@ class AppListView(ListView): * The template displays all items of a model in a table, each row has a unique id """ - default_template_name = 'meta/app_list.html' + + default_template_name = "meta/app_list.html" app_name = None - ordering = ['category__name'] + ordering = ["category__name"] title = "Quellen" def __init__(self, *args, **kwargs): super(AppListView, self).__init__(*args, **kwargs) if self.app_name is not None: - self.queryset = self.model.objects.filter( - app_name=self.app_name) + self.queryset = self.model.objects.filter(app_name=self.app_name) else: self.queryset = self.model.objects.all() def get_context_data(self, *, object_list=None, **kwargs): # pylint: disable=protected-access context = super(AppListView, self).get_context_data( - object_list=object_list, **kwargs) + object_list=object_list, **kwargs + ) - context['title'] = self.title + context["title"] = self.title # Try to load base.html template from app: - base_template = 'meta/base.html' + base_template = "meta/base.html" if self.app_name is not None: base_template_file = os.path.join( - BASE_DIR, - self.app_name, - 'templates', - self.app_name, - 'base.html' + BASE_DIR, self.app_name, "templates", self.app_name, "base.html" ) if os.path.exists(base_template_file): - base_template = self.app_name + '/base.html' - context['base_template'] = base_template + base_template = self.app_name + "/base.html" + context["base_template"] = base_template # Highlight specific id: highlight_id = self.request.GET.get(self.model._meta.model_name, None) if ( - highlight_id is not None and - context['object_list'].filter(pk=highlight_id).exists() + highlight_id is not None + and context["object_list"].filter(pk=highlight_id).exists() ): - context['highlight'] = str(highlight_id) + context["highlight"] = str(highlight_id) # Highlight category: - category_id = self.request.GET.get('category', None) + category_id = self.request.GET.get("category", None) if ( - category_id is not None and - context['object_list'].filter( - category__pk=category_id).exists() + category_id is not None + and context["object_list"].filter(category__pk=category_id).exists() ): - context['category'] = str(category_id) + context["category"] = str(category_id) return context @@ -101,12 +97,13 @@ class AssumptionsView(AppListView): def get_context_data(self, *, object_list=None, **kwargs): context = super(AssumptionsView, self).get_context_data( - object_list=object_list, **kwargs) + object_list=object_list, **kwargs + ) if self.source_url is not None: - context['source_url'] = self.source_url + context["source_url"] = self.source_url else: if self.app_name is not None: - context['source_url'] = self.app_name + ':sources' + context["source_url"] = self.app_name + ":sources" else: - context['source_url'] = 'sources' + context["source_url"] = "sources" return context diff --git a/meta/widgets.py b/meta/widgets.py index 2b6bcfa..0acb118 100644 --- a/meta/widgets.py +++ b/meta/widgets.py @@ -1,32 +1,35 @@ from django.utils.safestring import mark_safe -class JsonWidget(): +class JsonWidget: def __init__(self, json): self.json = json def __convert_to_html(self, data, level=0): - html = '' + html = "" if isinstance(data, dict): - html += f'
'\ - if level > 0 else '
' + html += ( + f'
' + if level > 0 + else "
" + ) for key, value in data.items(): - html += f'{key}: {self.__convert_to_html(value, level+1)}' - html += '
' + html += f"{key}: {self.__convert_to_html(value, level+1)}" + html += "
" elif isinstance(data, list): html += f'
' for item in data: - html += f'{self.__convert_to_html(item, level+1)}' - html += '
' + html += f"{self.__convert_to_html(item, level+1)}" + html += "
" else: - html += f'{data}
' + html += f"{data}
" return html def render(self): - header = '' + header = "" if self.json["title"] != "": header += f'

{self.json["title"]}

' if self.json["description"] != "": diff --git a/setup.py b/setup.py index 90d7c39..89b81b8 100644 --- a/setup.py +++ b/setup.py @@ -2,22 +2,23 @@ from setuptools import setup, find_packages -with open('requirements.txt') as f: +with open("requirements.txt") as f: requirements = f.read().splitlines() git_requirements = [ f"{req.split('#egg=')[1]} @ {req}" - for req in requirements if req.startswith('git') + for req in requirements + if req.startswith("git") ] - normal_requirements = [req for req in requirements if not req.startswith('git')] + normal_requirements = [req for req in requirements if not req.startswith("git")] setup( - name='wam', - version='0.1.3', + name="wam", + version="0.1.3", packages=find_packages(), - license='GNU Affero General Public License v3.0', - author='henhuy, nesnoj, Bachibouzouk, christian-rli, 4lm', - description='WAM - Web Applications & Maps', - url='https://github.com/rl-institut/WAM', + license="GNU Affero General Public License v3.0", + author="henhuy, nesnoj, Bachibouzouk, christian-rli, 4lm", + description="WAM - Web Applications & Maps", + url="https://github.com/rl-institut/WAM", install_requires=normal_requirements + git_requirements, - scripts=['manage.py'] + scripts=["manage.py"], ) diff --git a/static/ionicons-2.0.1/builder/generate.py b/static/ionicons-2.0.1/builder/generate.py index 8bfff53..39ae07c 100644 --- a/static/ionicons-2.0.1/builder/generate.py +++ b/static/ionicons-2.0.1/builder/generate.py @@ -4,315 +4,316 @@ BUILDER_PATH = os.path.dirname(os.path.abspath(__file__)) -ROOT_PATH = os.path.join(BUILDER_PATH, '..') -FONTS_FOLDER_PATH = os.path.join(ROOT_PATH, 'fonts') -CSS_FOLDER_PATH = os.path.join(ROOT_PATH, 'css') -SCSS_FOLDER_PATH = os.path.join(ROOT_PATH, 'scss') -LESS_FOLDER_PATH = os.path.join(ROOT_PATH, 'less') +ROOT_PATH = os.path.join(BUILDER_PATH, "..") +FONTS_FOLDER_PATH = os.path.join(ROOT_PATH, "fonts") +CSS_FOLDER_PATH = os.path.join(ROOT_PATH, "css") +SCSS_FOLDER_PATH = os.path.join(ROOT_PATH, "scss") +LESS_FOLDER_PATH = os.path.join(ROOT_PATH, "less") def main(): - generate_font_files() + generate_font_files() - data = get_build_data() + data = get_build_data() - rename_svg_glyph_names(data) - generate_scss(data) - generate_less(data) - generate_cheatsheet(data) - generate_component_json(data) - generate_composer_json(data) - generate_bower_json(data) + rename_svg_glyph_names(data) + generate_scss(data) + generate_less(data) + generate_cheatsheet(data) + generate_component_json(data) + generate_composer_json(data) + generate_bower_json(data) def generate_font_files(): - print "Generate Fonts" - cmd = "fontforge -script %s/scripts/generate_font.py" % (BUILDER_PATH) - call(cmd, shell=True) + print "Generate Fonts" + cmd = "fontforge -script %s/scripts/generate_font.py" % (BUILDER_PATH) + call(cmd, shell=True) def rename_svg_glyph_names(data): - # hacky and slow (but safe) way to rename glyph-name attributes - svg_path = os.path.join(FONTS_FOLDER_PATH, 'ionicons.svg') - svg_file = open(svg_path, 'r+') - svg_text = svg_file.read() - svg_file.seek(0) + # hacky and slow (but safe) way to rename glyph-name attributes + svg_path = os.path.join(FONTS_FOLDER_PATH, "ionicons.svg") + svg_file = open(svg_path, "r+") + svg_text = svg_file.read() + svg_file.seek(0) - for ionicon in data['icons']: - # uniF2CA - org_name = 'uni%s' % (ionicon['code'].replace('0x', '').upper()) - ion_name = 'ion-%s' % (ionicon['name']) - svg_text = svg_text.replace(org_name, ion_name) + for ionicon in data["icons"]: + # uniF2CA + org_name = "uni%s" % (ionicon["code"].replace("0x", "").upper()) + ion_name = "ion-%s" % (ionicon["name"]) + svg_text = svg_text.replace(org_name, ion_name) - svg_file.write(svg_text) - svg_file.close() + svg_file.write(svg_text) + svg_file.close() def generate_less(data): - print "Generate LESS" - font_name = data['name'] - font_version = data['version'] - css_prefix = data['prefix'] - variables_file_path = os.path.join(LESS_FOLDER_PATH, '_ionicons-variables.less') - icons_file_path = os.path.join(LESS_FOLDER_PATH, '_ionicons-icons.less') - - d = [] - d.append('/*!'); - d.append('Ionicons, v%s' % (font_version) ); - d.append('Created by Ben Sperry for the Ionic Framework, http://ionicons.com/'); - d.append('https://twitter.com/benjsperry https://twitter.com/ionicframework'); - d.append('MIT License: https://github.com/driftyco/ionicons'); - d.append('*/'); - d.append('// Ionicons Variables') - d.append('// --------------------------\n') - d.append('@ionicons-font-path: "../fonts";') - d.append('@ionicons-font-family: "%s";' % (font_name) ) - d.append('@ionicons-version: "%s";' % (font_version) ) - d.append('@ionicons-prefix: %s;' % (css_prefix) ) - d.append('') - for ionicon in data['icons']: - chr_code = ionicon['code'].replace('0x', '\\') - d.append('@ionicon-var-%s: "%s";' % (ionicon['name'], chr_code) ) - f = open(variables_file_path, 'w') - f.write( '\n'.join(d) ) - f.close() - - d = [] - d.append('// Ionicons Icons') - d.append('// --------------------------\n') - - group = [ '.%s' % (data['name'].lower()) ] - for ionicon in data['icons']: - group.append('.@{ionicons-prefix}%s:before' % (ionicon['name']) ) - - d.append( ',\n'.join(group) ) - - d.append('{') - d.append(' &:extend(.ion);') - d.append('}') - - for ionicon in data['icons']: - chr_code = ionicon['code'].replace('0x', '\\') - d.append('.@{ionicons-prefix}%s:before { content: @ionicon-var-%s; }' % (ionicon['name'], ionicon['name']) ) - - f = open(icons_file_path, 'w') - f.write( '\n'.join(d) ) - f.close() + print "Generate LESS" + font_name = data["name"] + font_version = data["version"] + css_prefix = data["prefix"] + variables_file_path = os.path.join(LESS_FOLDER_PATH, "_ionicons-variables.less") + icons_file_path = os.path.join(LESS_FOLDER_PATH, "_ionicons-icons.less") + + d = [] + d.append("/*!") + d.append("Ionicons, v%s" % (font_version)) + d.append("Created by Ben Sperry for the Ionic Framework, http://ionicons.com/") + d.append("https://twitter.com/benjsperry https://twitter.com/ionicframework") + d.append("MIT License: https://github.com/driftyco/ionicons") + d.append("*/") + d.append("// Ionicons Variables") + d.append("// --------------------------\n") + d.append('@ionicons-font-path: "../fonts";') + d.append('@ionicons-font-family: "%s";' % (font_name)) + d.append('@ionicons-version: "%s";' % (font_version)) + d.append("@ionicons-prefix: %s;" % (css_prefix)) + d.append("") + for ionicon in data["icons"]: + chr_code = ionicon["code"].replace("0x", "\\") + d.append('@ionicon-var-%s: "%s";' % (ionicon["name"], chr_code)) + f = open(variables_file_path, "w") + f.write("\n".join(d)) + f.close() + + d = [] + d.append("// Ionicons Icons") + d.append("// --------------------------\n") + + group = [".%s" % (data["name"].lower())] + for ionicon in data["icons"]: + group.append(".@{ionicons-prefix}%s:before" % (ionicon["name"])) + + d.append(",\n".join(group)) + + d.append("{") + d.append(" &:extend(.ion);") + d.append("}") + + for ionicon in data["icons"]: + chr_code = ionicon["code"].replace("0x", "\\") + d.append( + ".@{ionicons-prefix}%s:before { content: @ionicon-var-%s; }" + % (ionicon["name"], ionicon["name"]) + ) + + f = open(icons_file_path, "w") + f.write("\n".join(d)) + f.close() def generate_scss(data): - print "Generate SCSS" - font_name = data['name'] - font_version = data['version'] - css_prefix = data['prefix'] - variables_file_path = os.path.join(SCSS_FOLDER_PATH, '_ionicons-variables.scss') - icons_file_path = os.path.join(SCSS_FOLDER_PATH, '_ionicons-icons.scss') - - d = [] - d.append('// Ionicons Variables') - d.append('// --------------------------\n') - d.append('$ionicons-font-path: "../fonts" !default;') - d.append('$ionicons-font-family: "%s" !default;' % (font_name) ) - d.append('$ionicons-version: "%s" !default;' % (font_version) ) - d.append('$ionicons-prefix: %s !default;' % (css_prefix) ) - d.append('') - for ionicon in data['icons']: - chr_code = ionicon['code'].replace('0x', '\\') - d.append('$ionicon-var-%s: "%s";' % (ionicon['name'], chr_code) ) - f = open(variables_file_path, 'w') - f.write( '\n'.join(d) ) - f.close() - - d = [] - d.append('// Ionicons Icons') - d.append('// --------------------------\n') - - group = [ '.%s' % (data['name'].lower()) ] - for ionicon in data['icons']: - group.append('.#{$ionicons-prefix}%s:before' % (ionicon['name']) ) - - d.append( ',\n'.join(group) ) - - d.append('{') - d.append(' @extend .ion;') - d.append('}') - - for ionicon in data['icons']: - chr_code = ionicon['code'].replace('0x', '\\') - d.append('.#{$ionicons-prefix}%s:before { content: $ionicon-var-%s; }' % (ionicon['name'], ionicon['name']) ) - - f = open(icons_file_path, 'w') - f.write( '\n'.join(d) ) - f.close() - - generate_css_from_scss(data) + print "Generate SCSS" + font_name = data["name"] + font_version = data["version"] + css_prefix = data["prefix"] + variables_file_path = os.path.join(SCSS_FOLDER_PATH, "_ionicons-variables.scss") + icons_file_path = os.path.join(SCSS_FOLDER_PATH, "_ionicons-icons.scss") + + d = [] + d.append("// Ionicons Variables") + d.append("// --------------------------\n") + d.append('$ionicons-font-path: "../fonts" !default;') + d.append('$ionicons-font-family: "%s" !default;' % (font_name)) + d.append('$ionicons-version: "%s" !default;' % (font_version)) + d.append("$ionicons-prefix: %s !default;" % (css_prefix)) + d.append("") + for ionicon in data["icons"]: + chr_code = ionicon["code"].replace("0x", "\\") + d.append('$ionicon-var-%s: "%s";' % (ionicon["name"], chr_code)) + f = open(variables_file_path, "w") + f.write("\n".join(d)) + f.close() + + d = [] + d.append("// Ionicons Icons") + d.append("// --------------------------\n") + + group = [".%s" % (data["name"].lower())] + for ionicon in data["icons"]: + group.append(".#{$ionicons-prefix}%s:before" % (ionicon["name"])) + + d.append(",\n".join(group)) + + d.append("{") + d.append(" @extend .ion;") + d.append("}") + + for ionicon in data["icons"]: + chr_code = ionicon["code"].replace("0x", "\\") + d.append( + ".#{$ionicons-prefix}%s:before { content: $ionicon-var-%s; }" + % (ionicon["name"], ionicon["name"]) + ) + + f = open(icons_file_path, "w") + f.write("\n".join(d)) + f.close() + + generate_css_from_scss(data) def generate_css_from_scss(data): - print "Generate CSS From SCSS" + print "Generate CSS From SCSS" - scss_file_path = os.path.join(SCSS_FOLDER_PATH, 'ionicons.scss') - css_file_path = os.path.join(CSS_FOLDER_PATH, 'ionicons.css') - css_min_file_path = os.path.join(CSS_FOLDER_PATH, 'ionicons.min.css') + scss_file_path = os.path.join(SCSS_FOLDER_PATH, "ionicons.scss") + css_file_path = os.path.join(CSS_FOLDER_PATH, "ionicons.css") + css_min_file_path = os.path.join(CSS_FOLDER_PATH, "ionicons.min.css") - cmd = "sass %s %s --style compact" % (scss_file_path, css_file_path) - call(cmd, shell=True) + cmd = "sass %s %s --style compact" % (scss_file_path, css_file_path) + call(cmd, shell=True) - print "Generate Minified CSS From SCSS" - cmd = "sass %s %s --style compressed" % (scss_file_path, css_min_file_path) - call(cmd, shell=True) + print "Generate Minified CSS From SCSS" + cmd = "sass %s %s --style compressed" % (scss_file_path, css_min_file_path) + call(cmd, shell=True) def generate_cheatsheet(data): - print "Generate Cheatsheet" + print "Generate Cheatsheet" - cheatsheet_file_path = os.path.join(ROOT_PATH, 'cheatsheet.html') - template_path = os.path.join(BUILDER_PATH, 'cheatsheet', 'template.html') - icon_row_path = os.path.join(BUILDER_PATH, 'cheatsheet', 'icon-row.html') + cheatsheet_file_path = os.path.join(ROOT_PATH, "cheatsheet.html") + template_path = os.path.join(BUILDER_PATH, "cheatsheet", "template.html") + icon_row_path = os.path.join(BUILDER_PATH, "cheatsheet", "icon-row.html") - f = open(template_path, 'r') - template_html = f.read() - f.close() + f = open(template_path, "r") + template_html = f.read() + f.close() - f = open(icon_row_path, 'r') - icon_row_template = f.read() - f.close() + f = open(icon_row_path, "r") + icon_row_template = f.read() + f.close() - content = [] + content = [] - for ionicon in data['icons']: - css_code = ionicon['code'].replace('0x', '\\') - escaped_html_code = ionicon['code'].replace('0x', '&#x') + ';' - html_code = ionicon['code'].replace('0x', '&#x') + ';' - item_row = icon_row_template + for ionicon in data["icons"]: + css_code = ionicon["code"].replace("0x", "\\") + escaped_html_code = ionicon["code"].replace("0x", "&#x") + ";" + html_code = ionicon["code"].replace("0x", "&#x") + ";" + item_row = icon_row_template - item_row = item_row.replace('{{name}}', ionicon['name']) - item_row = item_row.replace('{{prefix}}', data['prefix']) - item_row = item_row.replace('{{css_code}}', css_code) - item_row = item_row.replace('{{escaped_html_code}}', escaped_html_code) - item_row = item_row.replace('{{html_code}}', html_code) + item_row = item_row.replace("{{name}}", ionicon["name"]) + item_row = item_row.replace("{{prefix}}", data["prefix"]) + item_row = item_row.replace("{{css_code}}", css_code) + item_row = item_row.replace("{{escaped_html_code}}", escaped_html_code) + item_row = item_row.replace("{{html_code}}", html_code) - content.append(item_row) + content.append(item_row) - template_html = template_html.replace("{{font_name}}", data["name"]) - template_html = template_html.replace("{{font_version}}", data["version"]) - template_html = template_html.replace("{{icon_count}}", str(len(data["icons"])) ) - template_html = template_html.replace("{{content}}", '\n'.join(content) ) + template_html = template_html.replace("{{font_name}}", data["name"]) + template_html = template_html.replace("{{font_version}}", data["version"]) + template_html = template_html.replace("{{icon_count}}", str(len(data["icons"]))) + template_html = template_html.replace("{{content}}", "\n".join(content)) - f = open(cheatsheet_file_path, 'w') - f.write(template_html) - f.close() + f = open(cheatsheet_file_path, "w") + f.write(template_html) + f.close() def generate_component_json(data): - print "Generate component.json" - d = { - "name": data['name'], - "repo": "driftyco/ionicons", - "description": "The premium icon font for Ionic Framework.", - "version": data['version'], - "keywords": [], - "dependencies": {}, - "development": {}, - "license": "MIT", - "styles": [ - "css/%s.css" % (data['name'].lower()) - ], - "fonts": [ - "fonts/%s.eot" % (data['name'].lower()), - "fonts/%s.svg" % (data['name'].lower()), - "fonts/%s.ttf" % (data['name'].lower()), - "fonts/%s.woff" % (data['name'].lower()) - ] - } - txt = json.dumps(d, indent=4, separators=(',', ': ')) - - component_file_path = os.path.join(ROOT_PATH, 'component.json') - f = open(component_file_path, 'w') - f.write(txt) - f.close() + print "Generate component.json" + d = { + "name": data["name"], + "repo": "driftyco/ionicons", + "description": "The premium icon font for Ionic Framework.", + "version": data["version"], + "keywords": [], + "dependencies": {}, + "development": {}, + "license": "MIT", + "styles": ["css/%s.css" % (data["name"].lower())], + "fonts": [ + "fonts/%s.eot" % (data["name"].lower()), + "fonts/%s.svg" % (data["name"].lower()), + "fonts/%s.ttf" % (data["name"].lower()), + "fonts/%s.woff" % (data["name"].lower()), + ], + } + txt = json.dumps(d, indent=4, separators=(",", ": ")) + + component_file_path = os.path.join(ROOT_PATH, "component.json") + f = open(component_file_path, "w") + f.write(txt) + f.close() def generate_composer_json(data): - print "Generate composer.json" - d = { - "name": "driftyco/ionicons", - "description": "The premium icon font for Ionic Framework.", - "keywords": [ "fonts", "icon font", "icons", "ionic", "web font"], - "homepage": "http://ionicons.com/", - "authors": [ - { - "name": "Ben Sperry", - "email": "ben@drifty.com", - "role": "Designer", - "homepage": "https://twitter.com/benjsperry" - }, - { - "name": "Adam Bradley", - "email": "adam@drifty.com", - "role": "Developer", - "homepage": "https://twitter.com/adamdbradley" - }, - { - "name": "Max Lynch", - "email": "max@drifty.com", - "role": "Developer", - "homepage": "https://twitter.com/maxlynch" - } - ], - "extra": {}, - "license": [ "MIT" ] - } - txt = json.dumps(d, indent=4, separators=(',', ': ')) - - composer_file_path = os.path.join(ROOT_PATH, 'composer.json') - f = open(composer_file_path, 'w') - f.write(txt) - f.close() + print "Generate composer.json" + d = { + "name": "driftyco/ionicons", + "description": "The premium icon font for Ionic Framework.", + "keywords": ["fonts", "icon font", "icons", "ionic", "web font"], + "homepage": "http://ionicons.com/", + "authors": [ + { + "name": "Ben Sperry", + "email": "ben@drifty.com", + "role": "Designer", + "homepage": "https://twitter.com/benjsperry", + }, + { + "name": "Adam Bradley", + "email": "adam@drifty.com", + "role": "Developer", + "homepage": "https://twitter.com/adamdbradley", + }, + { + "name": "Max Lynch", + "email": "max@drifty.com", + "role": "Developer", + "homepage": "https://twitter.com/maxlynch", + }, + ], + "extra": {}, + "license": ["MIT"], + } + txt = json.dumps(d, indent=4, separators=(",", ": ")) + + composer_file_path = os.path.join(ROOT_PATH, "composer.json") + f = open(composer_file_path, "w") + f.write(txt) + f.close() def generate_bower_json(data): - print "Generate bower.json" - d = { - "name": data['name'], - "version": data['version'], - "homepage": "https://github.com/driftyco/ionicons", - "authors": [ - "Ben Sperry ", - "Adam Bradley ", - "Max Lynch " - ], - "description": "Ionicons - free and beautiful icons from the creators of Ionic Framework", - "main": [ - "css/%s.css" % (data['name'].lower()), - "fonts/*" - ], - "keywords": [ "fonts", "icon font", "icons", "ionic", "web font"], - "license": "MIT", - "ignore": [ - "**/.*", - "builder", - "node_modules", - "bower_components", - "test", - "tests" - ] - } - txt = json.dumps(d, indent=4, separators=(',', ': ')) - - bower_file_path = os.path.join(ROOT_PATH, 'bower.json') - f = open(bower_file_path, 'w') - f.write(txt) - f.close() + print "Generate bower.json" + d = { + "name": data["name"], + "version": data["version"], + "homepage": "https://github.com/driftyco/ionicons", + "authors": [ + "Ben Sperry ", + "Adam Bradley ", + "Max Lynch ", + ], + "description": "Ionicons - free and beautiful icons from the creators of Ionic Framework", + "main": ["css/%s.css" % (data["name"].lower()), "fonts/*"], + "keywords": ["fonts", "icon font", "icons", "ionic", "web font"], + "license": "MIT", + "ignore": [ + "**/.*", + "builder", + "node_modules", + "bower_components", + "test", + "tests", + ], + } + txt = json.dumps(d, indent=4, separators=(",", ": ")) + + bower_file_path = os.path.join(ROOT_PATH, "bower.json") + f = open(bower_file_path, "w") + f.write(txt) + f.close() def get_build_data(): - build_data_path = os.path.join(BUILDER_PATH, 'build_data.json') - f = open(build_data_path, 'r') - data = json.loads(f.read()) - f.close() - return data + build_data_path = os.path.join(BUILDER_PATH, "build_data.json") + f = open(build_data_path, "r") + data = json.loads(f.read()) + f.close() + return data if __name__ == "__main__": - main() + main() diff --git a/static/ionicons-2.0.1/builder/scripts/eotlitetool.py b/static/ionicons-2.0.1/builder/scripts/eotlitetool.py index d3e253b..1898312 100644 --- a/static/ionicons-2.0.1/builder/scripts/eotlitetool.py +++ b/static/ionicons-2.0.1/builder/scripts/eotlitetool.py @@ -42,9 +42,9 @@ # OpenType file structure # http://www.microsoft.com/typography/otspec/otff.htm -# +# # Types: -# +# # BYTE 8-bit unsigned integer. # CHAR 8-bit signed integer. # USHORT 16-bit unsigned integer. @@ -52,111 +52,111 @@ # ULONG 32-bit unsigned integer. # Fixed 32-bit signed fixed-point number (16.16) # LONGDATETIME Date represented in number of seconds since 12:00 midnight, January 1, 1904. The value is represented as a signed 64-bit integer. -# +# # SFNT Header -# +# # Fixed sfnt version // 0x00010000 for version 1.0. # USHORT numTables // Number of tables. # USHORT searchRange // (Maximum power of 2 <= numTables) x 16. # USHORT entrySelector // Log2(maximum power of 2 <= numTables). # USHORT rangeShift // NumTables x 16-searchRange. -# +# # Table Directory -# +# # ULONG tag // 4-byte identifier. # ULONG checkSum // CheckSum for this table. # ULONG offset // Offset from beginning of TrueType font file. # ULONG length // Length of this table. -# +# # OS/2 Table (Version 4) -# +# # USHORT version // 0x0004 -# SHORT xAvgCharWidth -# USHORT usWeightClass -# USHORT usWidthClass -# USHORT fsType -# SHORT ySubscriptXSize -# SHORT ySubscriptYSize -# SHORT ySubscriptXOffset -# SHORT ySubscriptYOffset -# SHORT ySuperscriptXSize -# SHORT ySuperscriptYSize -# SHORT ySuperscriptXOffset -# SHORT ySuperscriptYOffset -# SHORT yStrikeoutSize -# SHORT yStrikeoutPosition -# SHORT sFamilyClass -# BYTE panose[10] +# SHORT xAvgCharWidth +# USHORT usWeightClass +# USHORT usWidthClass +# USHORT fsType +# SHORT ySubscriptXSize +# SHORT ySubscriptYSize +# SHORT ySubscriptXOffset +# SHORT ySubscriptYOffset +# SHORT ySuperscriptXSize +# SHORT ySuperscriptYSize +# SHORT ySuperscriptXOffset +# SHORT ySuperscriptYOffset +# SHORT yStrikeoutSize +# SHORT yStrikeoutPosition +# SHORT sFamilyClass +# BYTE panose[10] # ULONG ulUnicodeRange1 // Bits 0-31 # ULONG ulUnicodeRange2 // Bits 32-63 # ULONG ulUnicodeRange3 // Bits 64-95 # ULONG ulUnicodeRange4 // Bits 96-127 -# CHAR achVendID[4] -# USHORT fsSelection -# USHORT usFirstCharIndex -# USHORT usLastCharIndex -# SHORT sTypoAscender -# SHORT sTypoDescender -# SHORT sTypoLineGap -# USHORT usWinAscent -# USHORT usWinDescent +# CHAR achVendID[4] +# USHORT fsSelection +# USHORT usFirstCharIndex +# USHORT usLastCharIndex +# SHORT sTypoAscender +# SHORT sTypoDescender +# SHORT sTypoLineGap +# USHORT usWinAscent +# USHORT usWinDescent # ULONG ulCodePageRange1 // Bits 0-31 # ULONG ulCodePageRange2 // Bits 32-63 -# SHORT sxHeight -# SHORT sCapHeight -# USHORT usDefaultChar -# USHORT usBreakChar -# USHORT usMaxContext -# -# +# SHORT sxHeight +# SHORT sCapHeight +# USHORT usDefaultChar +# USHORT usBreakChar +# USHORT usMaxContext +# +# # The Naming Table is organized as follows: -# +# # [name table header] # [name records] # [string data] -# +# # Name Table Header -# +# # USHORT format // Format selector (=0). # USHORT count // Number of name records. # USHORT stringOffset // Offset to start of string storage (from start of table). -# +# # Name Record -# +# # USHORT platformID // Platform ID. # USHORT encodingID // Platform-specific encoding ID. # USHORT languageID // Language ID. # USHORT nameID // Name ID. # USHORT length // String length (in bytes). # USHORT offset // String offset from start of storage area (in bytes). -# +# # head Table -# +# # Fixed tableVersion // Table version number 0x00010000 for version 1.0. # Fixed fontRevision // Set by font manufacturer. # ULONG checkSumAdjustment // To compute: set it to 0, sum the entire font as ULONG, then store 0xB1B0AFBA - sum. # ULONG magicNumber // Set to 0x5F0F3CF5. -# USHORT flags +# USHORT flags # USHORT unitsPerEm // Valid range is from 16 to 16384. This value should be a power of 2 for fonts that have TrueType outlines. # LONGDATETIME created // Number of seconds since 12:00 midnight, January 1, 1904. 64-bit integer # LONGDATETIME modified // Number of seconds since 12:00 midnight, January 1, 1904. 64-bit integer # SHORT xMin // For all glyph bounding boxes. -# SHORT yMin -# SHORT xMax -# SHORT yMax +# SHORT yMin +# SHORT xMax +# SHORT yMax # USHORT macStyle # USHORT lowestRecPPEM // Smallest readable size in pixels. # SHORT fontDirectionHint # SHORT indexToLocFormat // 0 for short offsets, 1 for long. # SHORT glyphDataFormat // 0 for current format. -# -# -# +# +# +# # Embedded OpenType (EOT) file format # http://www.w3.org/Submission/EOT/ -# +# # EOT version 0x00020001 -# +# # An EOT font consists of a header with the original OpenType font # appended at the end. Most of the data in the EOT header is simply a # copy of data from specific tables within the font data. The exceptions @@ -165,15 +165,15 @@ # used. A null root string implies the font data can be used anywhere. # The EOT header is in little-endian byte order but the font data remains # in big-endian order as specified by the OpenType spec. -# +# # Overall structure: -# +# # [EOT header] # [EOT name records] # [font data] -# +# # EOT header -# +# # ULONG eotSize // Total structure length in bytes (including string and font data) # ULONG fontDataSize // Length of the OpenType font (FontData) in bytes # ULONG version // Version number of this format - 0x00020001 @@ -193,274 +193,318 @@ # ULONG checkSumAdjustment // head Table CheckSumAdjustment # ULONG reserved[4] // Reserved - must be 0 # USHORT padding1 // Padding - must be 0 -# +# # EOT name records -# +# # USHORT FamilyNameSize // Font family name size in bytes # BYTE FamilyName[FamilyNameSize] // Font family name (name ID = 1), little-endian UTF-16 # USHORT Padding2 // Padding - must be 0 -# +# # USHORT StyleNameSize // Style name size in bytes # BYTE StyleName[StyleNameSize] // Style name (name ID = 2), little-endian UTF-16 # USHORT Padding3 // Padding - must be 0 -# +# # USHORT VersionNameSize // Version name size in bytes # bytes VersionName[VersionNameSize] // Version name (name ID = 5), little-endian UTF-16 # USHORT Padding4 // Padding - must be 0 -# +# # USHORT FullNameSize // Full name size in bytes # BYTE FullName[FullNameSize] // Full name (name ID = 4), little-endian UTF-16 # USHORT Padding5 // Padding - must be 0 -# +# # USHORT RootStringSize // Root string size in bytes # BYTE RootString[RootStringSize] // Root string, little-endian UTF-16 - import optparse import struct + class FontError(Exception): """Error related to font handling""" + pass + def multichar(str): - vals = struct.unpack('4B', str[:4]) + vals = struct.unpack("4B", str[:4]) return (vals[0] << 24) + (vals[1] << 16) + (vals[2] << 8) + vals[3] + def multicharval(v): - return struct.pack('4B', (v >> 24) & 0xFF, (v >> 16) & 0xFF, (v >> 8) & 0xFF, v & 0xFF) + return struct.pack( + "4B", (v >> 24) & 0xFF, (v >> 16) & 0xFF, (v >> 8) & 0xFF, v & 0xFF + ) + class EOT: EOT_VERSION = 0x00020001 - EOT_MAGIC_NUMBER = 0x504c + EOT_MAGIC_NUMBER = 0x504C EOT_DEFAULT_CHARSET = 0x01 - EOT_FAMILY_NAME_INDEX = 0 # order of names in variable portion of EOT header + EOT_FAMILY_NAME_INDEX = 0 # order of names in variable portion of EOT header EOT_STYLE_NAME_INDEX = 1 EOT_VERSION_NAME_INDEX = 2 EOT_FULL_NAME_INDEX = 3 EOT_NUM_NAMES = 4 - - EOT_HEADER_PACK = '<4L10B2BL2H7L18x' + + EOT_HEADER_PACK = "<4L10B2BL2H7L18x" + class OpenType: - SFNT_CFF = multichar('OTTO') # Postscript CFF SFNT version - SFNT_TRUE = 0x10000 # Standard TrueType version - SFNT_APPLE = multichar('true') # Apple TrueType version - - SFNT_UNPACK = '>I4H' - TABLE_DIR_UNPACK = '>4I' - - TABLE_HEAD = multichar('head') # TrueType table tags - TABLE_NAME = multichar('name') - TABLE_OS2 = multichar('OS/2') - TABLE_GLYF = multichar('glyf') - TABLE_CFF = multichar('CFF ') - + SFNT_CFF = multichar("OTTO") # Postscript CFF SFNT version + SFNT_TRUE = 0x10000 # Standard TrueType version + SFNT_APPLE = multichar("true") # Apple TrueType version + + SFNT_UNPACK = ">I4H" + TABLE_DIR_UNPACK = ">4I" + + TABLE_HEAD = multichar("head") # TrueType table tags + TABLE_NAME = multichar("name") + TABLE_OS2 = multichar("OS/2") + TABLE_GLYF = multichar("glyf") + TABLE_CFF = multichar("CFF ") + OS2_FSSELECTION_ITALIC = 0x1 - OS2_UNPACK = '>4xH2xH22x10B4L4xH14x2L' - - HEAD_UNPACK = '>8xL' - - NAME_RECORD_UNPACK = '>6H' + OS2_UNPACK = ">4xH2xH22x10B4L4xH14x2L" + + HEAD_UNPACK = ">8xL" + + NAME_RECORD_UNPACK = ">6H" NAME_ID_FAMILY = 1 NAME_ID_STYLE = 2 NAME_ID_UNIQUE = 3 NAME_ID_FULL = 4 NAME_ID_VERSION = 5 NAME_ID_POSTSCRIPT = 6 - PLATFORM_ID_UNICODE = 0 # Mac OS uses this typically + PLATFORM_ID_UNICODE = 0 # Mac OS uses this typically PLATFORM_ID_MICROSOFT = 3 - ENCODING_ID_MICROSOFT_UNICODEBMP = 1 # with Microsoft platformID BMP-only Unicode encoding - LANG_ID_MICROSOFT_EN_US = 0x0409 # with Microsoft platformID EN US lang code + ENCODING_ID_MICROSOFT_UNICODEBMP = ( + 1 # with Microsoft platformID BMP-only Unicode encoding + ) + LANG_ID_MICROSOFT_EN_US = 0x0409 # with Microsoft platformID EN US lang code + def eotname(ttf): - i = ttf.rfind('.') + i = ttf.rfind(".") if i != -1: ttf = ttf[:i] - return ttf + '.eotlite' + return ttf + ".eotlite" + def readfont(f): - data = open(f, 'rb').read() + data = open(f, "rb").read() return data + def get_table_directory(data): """read the SFNT header and table directory""" datalen = len(data) sfntsize = struct.calcsize(OpenType.SFNT_UNPACK) if sfntsize > datalen: - raise FontError, 'truncated font data' + raise FontError, "truncated font data" sfntvers, numTables = struct.unpack(OpenType.SFNT_UNPACK, data[:sfntsize])[:2] if sfntvers != OpenType.SFNT_CFF and sfntvers != OpenType.SFNT_TRUE: - raise FontError, 'invalid font type'; - + raise FontError, "invalid font type" + font = {} - font['version'] = sfntvers - font['numTables'] = numTables - + font["version"] = sfntvers + font["numTables"] = numTables + # create set of offsets, lengths for tables table_dir_size = struct.calcsize(OpenType.TABLE_DIR_UNPACK) if sfntsize + table_dir_size * numTables > datalen: - raise FontError, 'truncated font data, table directory extends past end of data' + raise FontError, "truncated font data, table directory extends past end of data" table_dir = {} for i in range(0, numTables): start = sfntsize + i * table_dir_size end = start + table_dir_size - tag, check, bongo, dirlen = struct.unpack(OpenType.TABLE_DIR_UNPACK, data[start:end]) - table_dir[tag] = {'offset': bongo, 'length': dirlen, 'checksum': check} - - font['tableDir'] = table_dir - + tag, check, bongo, dirlen = struct.unpack( + OpenType.TABLE_DIR_UNPACK, data[start:end] + ) + table_dir[tag] = {"offset": bongo, "length": dirlen, "checksum": check} + + font["tableDir"] = table_dir + return font + def get_name_records(nametable): """reads through the name records within name table""" name = {} # read the header headersize = 6 - count, strOffset = struct.unpack('>2H', nametable[2:6]) + count, strOffset = struct.unpack(">2H", nametable[2:6]) namerecsize = struct.calcsize(OpenType.NAME_RECORD_UNPACK) if count * namerecsize + headersize > len(nametable): - raise FontError, 'names exceed size of name table' - name['count'] = count - name['strOffset'] = strOffset - + raise FontError, "names exceed size of name table" + name["count"] = count + name["strOffset"] = strOffset + # read through the name records namerecs = {} for i in range(0, count): start = headersize + i * namerecsize end = start + namerecsize - platformID, encodingID, languageID, nameID, namelen, offset = struct.unpack(OpenType.NAME_RECORD_UNPACK, nametable[start:end]) - if platformID != OpenType.PLATFORM_ID_MICROSOFT or \ - encodingID != OpenType.ENCODING_ID_MICROSOFT_UNICODEBMP or \ - languageID != OpenType.LANG_ID_MICROSOFT_EN_US: + platformID, encodingID, languageID, nameID, namelen, offset = struct.unpack( + OpenType.NAME_RECORD_UNPACK, nametable[start:end] + ) + if ( + platformID != OpenType.PLATFORM_ID_MICROSOFT + or encodingID != OpenType.ENCODING_ID_MICROSOFT_UNICODEBMP + or languageID != OpenType.LANG_ID_MICROSOFT_EN_US + ): continue - namerecs[nameID] = {'offset': offset, 'length': namelen} - - name['namerecords'] = namerecs + namerecs[nameID] = {"offset": offset, "length": namelen} + + name["namerecords"] = namerecs return name + def make_eot_name_headers(fontdata, nameTableDir): """extracts names from the name table and generates the names header portion of the EOT header""" - nameoffset = nameTableDir['offset'] - namelen = nameTableDir['length'] + nameoffset = nameTableDir["offset"] + namelen = nameTableDir["length"] name = get_name_records(fontdata[nameoffset : nameoffset + namelen]) - namestroffset = name['strOffset'] - namerecs = name['namerecords'] - - eotnames = (OpenType.NAME_ID_FAMILY, OpenType.NAME_ID_STYLE, OpenType.NAME_ID_VERSION, OpenType.NAME_ID_FULL) + namestroffset = name["strOffset"] + namerecs = name["namerecords"] + + eotnames = ( + OpenType.NAME_ID_FAMILY, + OpenType.NAME_ID_STYLE, + OpenType.NAME_ID_VERSION, + OpenType.NAME_ID_FULL, + ) nameheaders = [] for nameid in eotnames: if nameid in namerecs: namerecord = namerecs[nameid] - noffset = namerecord['offset'] - nlen = namerecord['length'] - nformat = '%dH' % (nlen / 2) # length is in number of bytes + noffset = namerecord["offset"] + nlen = namerecord["length"] + nformat = "%dH" % (nlen / 2) # length is in number of bytes start = nameoffset + namestroffset + noffset end = start + nlen - nstr = struct.unpack('>' + nformat, fontdata[start:end]) - nameheaders.append(struct.pack('" + nformat, fontdata[start:end]) + nameheaders.append(struct.pack(" os2Dir['length']: - raise FontError, 'OS/2 table invalid length' - - os2fields = struct.unpack(OpenType.OS2_UNPACK, fontdata[os2offset : os2offset + os2size]) - + + if os2size > os2Dir["length"]: + raise FontError, "OS/2 table invalid length" + + os2fields = struct.unpack( + OpenType.OS2_UNPACK, fontdata[os2offset : os2offset + os2size] + ) + panose = [] urange = [] codepage = [] - + weight, fsType = os2fields[:2] panose[:10] = os2fields[2:12] urange[:4] = os2fields[12:16] fsSelection = os2fields[16] codepage[:2] = os2fields[17:19] - + italic = fsSelection & OpenType.OS2_FSSELECTION_ITALIC - + # read in values from head table headDir = tableDir[OpenType.TABLE_HEAD] - headoffset = headDir['offset'] + headoffset = headDir["offset"] headsize = struct.calcsize(OpenType.HEAD_UNPACK) - - if headsize > headDir['length']: - raise FontError, 'head table invalid length' - - headfields = struct.unpack(OpenType.HEAD_UNPACK, fontdata[headoffset : headoffset + headsize]) + + if headsize > headDir["length"]: + raise FontError, "head table invalid length" + + headfields = struct.unpack( + OpenType.HEAD_UNPACK, fontdata[headoffset : headoffset + headsize] + ) checkSumAdjustment = headfields[0] - + # make name headers nameheaders = make_eot_name_headers(fontdata, tableDir[OpenType.TABLE_NAME]) rootstring = make_root_string() - + # calculate the total eot size - eotSize = struct.calcsize(EOT.EOT_HEADER_PACK) + len(nameheaders) + len(rootstring) + fontDataSize - fixed = struct.pack(EOT.EOT_HEADER_PACK, - *([eotSize, fontDataSize, version, flags] + panose + [charset, italic] + - [weight, fsType, magicNumber] + urange + codepage + [checkSumAdjustment])) - - return ''.join((fixed, nameheaders, rootstring)) - - + eotSize = ( + struct.calcsize(EOT.EOT_HEADER_PACK) + + len(nameheaders) + + len(rootstring) + + fontDataSize + ) + fixed = struct.pack( + EOT.EOT_HEADER_PACK, + *( + [eotSize, fontDataSize, version, flags] + + panose + + [charset, italic] + + [weight, fsType, magicNumber] + + urange + + codepage + + [checkSumAdjustment] + ) + ) + + return "".join((fixed, nameheaders, rootstring)) + + def write_eot_font(eot, header, data): - open(eot,'wb').write(''.join((header, data))) + open(eot, "wb").write("".join((header, data))) return + def main(): # deal with options p = optparse.OptionParser() - p.add_option('--output', '-o', default="world") + p.add_option("--output", "-o", default="world") options, args = p.parse_args() - + # iterate over font files for f in args: data = readfont(f) if len(data) == 0: - print 'Error reading %s' % f + print "Error reading %s" % f else: eot = eotname(f) header = make_eot_header(data) write_eot_font(eot, header, data) - -if __name__ == '__main__': + +if __name__ == "__main__": main() - - \ No newline at end of file diff --git a/static/ionicons-2.0.1/builder/scripts/generate_font.py b/static/ionicons-2.0.1/builder/scripts/generate_font.py index 8e0bed2..2987942 100755 --- a/static/ionicons-2.0.1/builder/scripts/generate_font.py +++ b/static/ionicons-2.0.1/builder/scripts/generate_font.py @@ -11,163 +11,180 @@ import copy SCRIPT_PATH = os.path.dirname(os.path.abspath(__file__)) -INPUT_SVG_DIR = os.path.join(SCRIPT_PATH, '..', '..', 'src') -OUTPUT_FONT_DIR = os.path.join(SCRIPT_PATH, '..', '..', 'fonts') -MANIFEST_PATH = os.path.join(SCRIPT_PATH, '..', 'manifest.json') -BUILD_DATA_PATH = os.path.join(SCRIPT_PATH, '..', 'build_data.json') +INPUT_SVG_DIR = os.path.join(SCRIPT_PATH, "..", "..", "src") +OUTPUT_FONT_DIR = os.path.join(SCRIPT_PATH, "..", "..", "fonts") +MANIFEST_PATH = os.path.join(SCRIPT_PATH, "..", "manifest.json") +BUILD_DATA_PATH = os.path.join(SCRIPT_PATH, "..", "build_data.json") AUTO_WIDTH = True KERNING = 15 -cp = 0xf100 +cp = 0xF100 m = md5.new() f = fontforge.font() -f.encoding = 'UnicodeFull' +f.encoding = "UnicodeFull" f.design_size = 16 f.em = 512 f.ascent = 448 f.descent = 64 -manifest_file = open(MANIFEST_PATH, 'r') +manifest_file = open(MANIFEST_PATH, "r") manifest_data = json.loads(manifest_file.read()) manifest_file.close() -print "Load Manifest, Icons: %s" % ( len(manifest_data['icons']) ) +print "Load Manifest, Icons: %s" % (len(manifest_data["icons"])) build_data = copy.deepcopy(manifest_data) -build_data['icons'] = [] +build_data["icons"] = [] -font_name = manifest_data['name'] -m.update(font_name + ';') -m.update(manifest_data['prefix'] + ';') +font_name = manifest_data["name"] +m.update(font_name + ";") +m.update(manifest_data["prefix"] + ";") for dirname, dirnames, filenames in os.walk(INPUT_SVG_DIR): - for filename in filenames: - name, ext = os.path.splitext(filename) - filePath = os.path.join(dirname, filename) - size = os.path.getsize(filePath) - - if ext in ['.svg', '.eps']: - - # see if this file is already in the manifest - chr_code = None - for ionicon in manifest_data['icons']: - if ionicon['name'] == name: - chr_code = ionicon['code'] - break - - if chr_code is None: - # this is a new src icon - print 'New Icon: \n - %s' % (name) - - while True: - chr_code = '0x%x' % (cp) - already_exists = False - for ionicon in manifest_data['icons']: - if ionicon.get('code') == chr_code: - already_exists = True - cp += 1 - chr_code = '0x%x' % (cp) - continue - if not already_exists: - break - - print ' - %s' % chr_code - manifest_data['icons'].append({ - 'name': name, - 'code': chr_code - }) - - build_data['icons'].append({ - 'name': name, - 'code': chr_code - }) - - if ext in ['.svg']: - # hack removal of tags - svgfile = open(filePath, 'r+') - tmpsvgfile = tempfile.NamedTemporaryFile(suffix=ext, delete=False) - svgtext = svgfile.read() - svgfile.seek(0) - - # replace the tags with 'nothing' - svgtext = svgtext.replace('', '') - svgtext = svgtext.replace('', '') - - tmpsvgfile.file.write(svgtext) - - svgfile.close() - tmpsvgfile.file.close() - - filePath = tmpsvgfile.name - # end hack - - m.update(name + str(size) + ';') - glyph = f.createChar( int(chr_code, 16) ) - glyph.importOutlines(filePath) - - # if we created a temporary file, let's clean it up - if tmpsvgfile: - os.unlink(tmpsvgfile.name) - - # set glyph size explicitly or automatically depending on autowidth - if AUTO_WIDTH: - glyph.left_side_bearing = glyph.right_side_bearing = 0 - glyph.round() - - # resize glyphs if autowidth is enabled - if AUTO_WIDTH: - f.autoWidth(0, 0, 512) - - fontfile = '%s/ionicons' % (OUTPUT_FONT_DIR) + for filename in filenames: + name, ext = os.path.splitext(filename) + filePath = os.path.join(dirname, filename) + size = os.path.getsize(filePath) + + if ext in [".svg", ".eps"]: + + # see if this file is already in the manifest + chr_code = None + for ionicon in manifest_data["icons"]: + if ionicon["name"] == name: + chr_code = ionicon["code"] + break + + if chr_code is None: + # this is a new src icon + print "New Icon: \n - %s" % (name) + + while True: + chr_code = "0x%x" % (cp) + already_exists = False + for ionicon in manifest_data["icons"]: + if ionicon.get("code") == chr_code: + already_exists = True + cp += 1 + chr_code = "0x%x" % (cp) + continue + if not already_exists: + break + + print " - %s" % chr_code + manifest_data["icons"].append({"name": name, "code": chr_code}) + + build_data["icons"].append({"name": name, "code": chr_code}) + + if ext in [".svg"]: + # hack removal of tags + svgfile = open(filePath, "r+") + tmpsvgfile = tempfile.NamedTemporaryFile(suffix=ext, delete=False) + svgtext = svgfile.read() + svgfile.seek(0) + + # replace the tags with 'nothing' + svgtext = svgtext.replace("", "") + svgtext = svgtext.replace("", "") + + tmpsvgfile.file.write(svgtext) + + svgfile.close() + tmpsvgfile.file.close() + + filePath = tmpsvgfile.name + # end hack + + m.update(name + str(size) + ";") + glyph = f.createChar(int(chr_code, 16)) + glyph.importOutlines(filePath) + + # if we created a temporary file, let's clean it up + if tmpsvgfile: + os.unlink(tmpsvgfile.name) + + # set glyph size explicitly or automatically depending on autowidth + if AUTO_WIDTH: + glyph.left_side_bearing = glyph.right_side_bearing = 0 + glyph.round() + + # resize glyphs if autowidth is enabled + if AUTO_WIDTH: + f.autoWidth(0, 0, 512) + + fontfile = "%s/ionicons" % (OUTPUT_FONT_DIR) build_hash = m.hexdigest() -if build_hash == manifest_data.get('build_hash'): - print "Source files unchanged, did not rebuild fonts" +if build_hash == manifest_data.get("build_hash"): + print "Source files unchanged, did not rebuild fonts" else: - manifest_data['build_hash'] = build_hash - - f.fontname = font_name - f.familyname = font_name - f.fullname = font_name - f.generate(fontfile + '.ttf') - f.generate(fontfile + '.svg') - - # Fix SVG header for webkit - # from: https://github.com/fontello/font-builder/blob/master/bin/fontconvert.py - svgfile = open(fontfile + '.svg', 'r+') - svgtext = svgfile.read() - svgfile.seek(0) - svgfile.write(svgtext.replace('''''', '''''')) - svgfile.close() - - scriptPath = os.path.dirname(os.path.realpath(__file__)) - try: - subprocess.Popen([scriptPath + '/sfnt2woff', fontfile + '.ttf'], stdout=subprocess.PIPE) - except OSError: - # If the local version of sfnt2woff fails (i.e., on Linux), try to use the - # global version. This allows us to avoid forcing OS X users to compile - # sfnt2woff from source, simplifying install. - subprocess.call(['sfnt2woff', fontfile + '.ttf']) - - # eotlitetool.py script to generate IE7-compatible .eot fonts - subprocess.call('python ' + scriptPath + '/eotlitetool.py ' + fontfile + '.ttf -o ' + fontfile + '.eot', shell=True) - subprocess.call('mv ' + fontfile + '.eotlite ' + fontfile + '.eot', shell=True) - - # Hint the TTF file - subprocess.call('ttfautohint -s -f -n ' + fontfile + '.ttf ' + fontfile + '-hinted.ttf > /dev/null 2>&1 && mv ' + fontfile + '-hinted.ttf ' + fontfile + '.ttf', shell=True) - - manifest_data['icons'] = sorted(manifest_data['icons'], key=lambda k: k['name']) - build_data['icons'] = sorted(build_data['icons'], key=lambda k: k['name']) - - print "Save Manifest, Icons: %s" % ( len(manifest_data['icons']) ) - f = open(MANIFEST_PATH, 'w') - f.write( json.dumps(manifest_data, indent=2, separators=(',', ': ')) ) - f.close() - - print "Save Build, Icons: %s" % ( len(build_data['icons']) ) - f = open(BUILD_DATA_PATH, 'w') - f.write( json.dumps(build_data, indent=2, separators=(',', ': ')) ) - f.close() - + manifest_data["build_hash"] = build_hash + + f.fontname = font_name + f.familyname = font_name + f.fullname = font_name + f.generate(fontfile + ".ttf") + f.generate(fontfile + ".svg") + + # Fix SVG header for webkit + # from: https://github.com/fontello/font-builder/blob/master/bin/fontconvert.py + svgfile = open(fontfile + ".svg", "r+") + svgtext = svgfile.read() + svgfile.seek(0) + svgfile.write( + svgtext.replace("""""", """""") + ) + svgfile.close() + + scriptPath = os.path.dirname(os.path.realpath(__file__)) + try: + subprocess.Popen( + [scriptPath + "/sfnt2woff", fontfile + ".ttf"], stdout=subprocess.PIPE + ) + except OSError: + # If the local version of sfnt2woff fails (i.e., on Linux), try to use the + # global version. This allows us to avoid forcing OS X users to compile + # sfnt2woff from source, simplifying install. + subprocess.call(["sfnt2woff", fontfile + ".ttf"]) + + # eotlitetool.py script to generate IE7-compatible .eot fonts + subprocess.call( + "python " + + scriptPath + + "/eotlitetool.py " + + fontfile + + ".ttf -o " + + fontfile + + ".eot", + shell=True, + ) + subprocess.call("mv " + fontfile + ".eotlite " + fontfile + ".eot", shell=True) + + # Hint the TTF file + subprocess.call( + "ttfautohint -s -f -n " + + fontfile + + ".ttf " + + fontfile + + "-hinted.ttf > /dev/null 2>&1 && mv " + + fontfile + + "-hinted.ttf " + + fontfile + + ".ttf", + shell=True, + ) + + manifest_data["icons"] = sorted(manifest_data["icons"], key=lambda k: k["name"]) + build_data["icons"] = sorted(build_data["icons"], key=lambda k: k["name"]) + + print "Save Manifest, Icons: %s" % (len(manifest_data["icons"])) + f = open(MANIFEST_PATH, "w") + f.write(json.dumps(manifest_data, indent=2, separators=(",", ": "))) + f.close() + + print "Save Build, Icons: %s" % (len(build_data["icons"])) + f = open(BUILD_DATA_PATH, "w") + f.write(json.dumps(build_data, indent=2, separators=(",", ": "))) + f.close() diff --git a/templatetags/labels.py b/templatetags/labels.py index 5a8139b..b9c9e48 100644 --- a/templatetags/labels.py +++ b/templatetags/labels.py @@ -1,4 +1,3 @@ - from django import template from django.utils.safestring import mark_safe @@ -21,17 +20,17 @@ def label(context, value, safe=False, app=None): app = get_app_from_request(context.request) except AttributeError: raise AttributeError( - 'Current app could not be found. ' - 'This may occur in widget templates as they do not return ' - 'request object. In this case, you can set app manually.' + "Current app could not be found. " + "This may occur in widget templates as they do not return " + "request object. In this case, you can set app manually." ) - if app == '': + if app == "": return None current = APP_LABELS.get(app) if current is None: return None - keys = value.split(':') + keys = value.split(":") for key in keys: current = current.get(key) if current is None: diff --git a/templatetags/simple.py b/templatetags/simple.py index e6430f7..2ce6b85 100644 --- a/templatetags/simple.py +++ b/templatetags/simple.py @@ -1,4 +1,3 @@ - from django import template register = template.Library() diff --git a/user_sessions/__init__.py b/user_sessions/__init__.py index accd9c2..232944c 100644 --- a/user_sessions/__init__.py +++ b/user_sessions/__init__.py @@ -1 +1 @@ -default_app_config = 'user_sessions.apps.UserSessionsConfig' +default_app_config = "user_sessions.apps.UserSessionsConfig" diff --git a/user_sessions/apps.py b/user_sessions/apps.py index 618f5eb..02b47ba 100644 --- a/user_sessions/apps.py +++ b/user_sessions/apps.py @@ -1,15 +1,15 @@ - from django.apps import AppConfig from wam import settings class UserSessionsConfig(AppConfig): - name = 'user_sessions' + name = "user_sessions" def ready(self): """This function is executed right after project settings""" # Initialize user sessions: from user_sessions.sessions import SessionData + settings.SESSION_DATA = SessionData() diff --git a/user_sessions/sessions.py b/user_sessions/sessions.py index 41014be..5e0e40d 100644 --- a/user_sessions/sessions.py +++ b/user_sessions/sessions.py @@ -2,7 +2,7 @@ from utils.shortcuts import get_app_from_request -class SessionData(): +class SessionData: def __init__(self): self.sessions = defaultdict(dict) @@ -16,9 +16,8 @@ def start_session(self, request, session_obj): def get_session(self, request): # pylint: disable=no-else-raise app = get_app_from_request(request) - user_session = self.sessions[app].get( - request.session.session_key, None) + user_session = self.sessions[app].get(request.session.session_key, None) if user_session is None: - raise KeyError('Session not found') + raise KeyError("Session not found") else: return user_session diff --git a/user_sessions/urls.py b/user_sessions/urls.py index 517d520..ef860a5 100644 --- a/user_sessions/urls.py +++ b/user_sessions/urls.py @@ -1,4 +1,3 @@ - from django.urls import path from wam.admin import wam_admin_site @@ -6,14 +5,14 @@ from user_sessions import views -app_name = 'user_sessions' +app_name = "user_sessions" url_patterns = [] admin_url_patterns = [ path( - 'sessions/manage', + "sessions/manage", wam_admin_site.admin_view(views.ManageView.as_view()), - name='manage' + name="manage", ), ] diff --git a/user_sessions/utils.py b/user_sessions/utils.py index 8ca9328..8f8228f 100644 --- a/user_sessions/utils.py +++ b/user_sessions/utils.py @@ -12,13 +12,15 @@ def check_session_method(func): This method is for class-based views """ + def func_wrapper(self, request, *args, **kwargs): try: session = SESSION_DATA.get_session(request) except KeyError: log_session_error(request) - return render(request, 'stemp/session_not_found.html') + return render(request, "stemp/session_not_found.html") return func(self, request, session=session, *args, **kwargs) + return func_wrapper @@ -28,13 +30,15 @@ def check_session(func): This function is for function-based views """ + def func_wrapper(request, *args, **kwargs): try: session = SESSION_DATA.get_session(request) except KeyError: log_session_error(request) - return render(request, 'stemp/session_not_found.html') + return render(request, "stemp/session_not_found.html") return func(request, session=session, *args, **kwargs) + return func_wrapper @@ -42,10 +46,8 @@ def log_session_error(request): app = get_app_from_request(request) err_msg = ( f'Session error for app "{app}":\n' - f'Session-Key: {request.session.session_key}\n' - f'Current session data:\n' + - '\n'.join( - [f'{k}: {str(v)}' for k, v in SESSION_DATA.sessions[app].items()] - ) + f"Session-Key: {request.session.session_key}\n" + f"Current session data:\n" + + "\n".join([f"{k}: {str(v)}" for k, v in SESSION_DATA.sessions[app].items()]) ) logging.error(err_msg) diff --git a/user_sessions/views.py b/user_sessions/views.py index 120b216..f20bb6c 100644 --- a/user_sessions/views.py +++ b/user_sessions/views.py @@ -1,11 +1,10 @@ - from django.views.generic import TemplateView from wam.settings import SESSION_DATA class ManageView(TemplateView): - template_name = 'user_sessions/manage.html' + template_name = "user_sessions/manage.html" def get_context_data(self, **kwargs): - return {'session_keys': SESSION_DATA.sessions.keys()} + return {"session_keys": SESSION_DATA.sessions.keys()} diff --git a/utils/constants.py b/utils/constants.py index c58e38f..0994e04 100644 --- a/utils/constants.py +++ b/utils/constants.py @@ -1,34 +1,32 @@ - from enum import Enum from collections import namedtuple AppInfo = namedtuple( - 'AppInfo', - ['category', 'name', 'url', 'closed', 'icon', 'url_arg'], + "AppInfo", ["category", "name", "url", "closed", "icon", "url_arg"], ) AppInfo.__new__.__defaults__ = (False, None, None) class AppCategory(Enum): - Web = 'web' - App = 'app' - Map = 'map' + Web = "web" + App = "app" + Map = "map" def label(self): return { - AppCategory.Web: 'Websites', - AppCategory.App: 'Applications', - AppCategory.Map: 'Maps' + AppCategory.Web: "Websites", + AppCategory.App: "Applications", + AppCategory.Map: "Maps", }.get(self) def goto(self): return { - AppCategory.Web: 'Zur Seite', - AppCategory.App: 'Zum Tool', - AppCategory.Map: 'Zur Karte' + AppCategory.Web: "Zur Seite", + AppCategory.App: "Zum Tool", + AppCategory.Map: "Zur Karte", }.get(self) class AppIcon(Enum): - Map = 'img/icons_custom/Icon_map_w.png' + Map = "img/icons_custom/Icon_map_w.png" diff --git a/utils/forms.py b/utils/forms.py index 34e4326..c63f4f2 100644 --- a/utils/forms.py +++ b/utils/forms.py @@ -3,17 +3,15 @@ class FeedbackForm(forms.Form): """Input form for feedback page""" - from_name = forms.CharField(required=False, - max_length=100, - label='Your Name (optional)') - from_email = forms.EmailField(required=False, - label='Email Address (optional)') - subject = forms.CharField(required=True, - max_length=100, - label='Subject') - message = forms.CharField(widget=forms.Textarea, - required=True, - label='Your Feedback') + + from_name = forms.CharField( + required=False, max_length=100, label="Your Name (optional)" + ) + from_email = forms.EmailField(required=False, label="Email Address (optional)") + subject = forms.CharField(required=True, max_length=100, label="Subject") + message = forms.CharField( + widget=forms.Textarea, required=True, label="Your Feedback" + ) def submit(self): pass diff --git a/utils/highcharts.py b/utils/highcharts.py index f68ece4..03d121d 100644 --- a/utils/highcharts.py +++ b/utils/highcharts.py @@ -1,4 +1,3 @@ - from itertools import count import pandas from django.utils.safestring import mark_safe @@ -8,102 +7,90 @@ # Full HC API reference: https://api.highcharts.com/highcharts/ RLI_THEME = { - 'credits': { - 'enabled': False - }, - 'colors': [ - '#fc8e65', '#55aae5', '#7fadb7', '#fce288', '#f69c3a', '#c28e5e', - '#a27b82', '#797097' + "credits": {"enabled": False}, + "colors": [ + "#fc8e65", + "#55aae5", + "#7fadb7", + "#fce288", + "#f69c3a", + "#c28e5e", + "#a27b82", + "#797097", ], - 'title': { - 'style': { - 'color': '#002E4F', - 'font': 'bold 1.2rem Roboto, "Trebuchet MS", Verdana, sans-serif' + "title": { + "style": { + "color": "#002E4F", + "font": 'bold 1.2rem Roboto, "Trebuchet MS", Verdana, sans-serif', } }, - 'subtitle': { - 'style': { - 'color': '#666', - 'font': 'bold 12px "Roboto Light", "Trebuchet MS", Verdana, sans-serif' + "subtitle": { + "style": { + "color": "#666", + "font": 'bold 12px "Roboto Light", "Trebuchet MS", Verdana, sans-serif', } }, - 'lang': { - 'decimalPoint': ',', - 'thousandsSep': '.' - }, - 'legend': { - 'itemStyle': { - 'font': '1rem "Roboto Light", Trebuchet MS, Verdana, sans-serif', - 'color': 'black' + "lang": {"decimalPoint": ",", "thousandsSep": "."}, + "legend": { + "itemStyle": { + "font": '1rem "Roboto Light", Trebuchet MS, Verdana, sans-serif', + "color": "black", }, - 'itemHoverStyle': { - 'color': 'gray' - } + "itemHoverStyle": {"color": "gray"}, + }, + "plotOptions": { + "series": {"dataLabels": {"style": {"fontWeight": None, "textOutline": None}}} }, - 'plotOptions': { - 'series': { - 'dataLabels': { - 'style': { - 'fontWeight': None, - 'textOutline': None - } - } - } - } } class Highchart(HC): - media_template = 'includes/highchart_media.html' + media_template = "includes/highchart_media.html" id_counter = count() def __init__(self, use_rli_theme=True, **kwargs): self.id = next(self.id_counter) - if 'renderTo' not in kwargs: - kwargs['renderTo'] = f'hc_{self.id}' + if "renderTo" not in kwargs: + kwargs["renderTo"] = f"hc_{self.id}" super(Highchart, self).__init__(**kwargs) if use_rli_theme: self.set_dict_options(RLI_THEME) def add_pandas_data_set(self, data, series_type=None, **kwargs): if series_type is None: - ValueError('No highcharts type specified.') + ValueError("No highcharts type specified.") if isinstance(data, pandas.Series): - self.add_data_set( - data.values.tolist(), series_type, data.name, **kwargs) + self.add_data_set(data.values.tolist(), series_type, data.name, **kwargs) elif isinstance(data, pandas.DataFrame): for column in data.columns: - if series_type == 'scatter': + if series_type == "scatter": self.add_data_set( data=[data[column].tolist()], series_type=series_type, name=column, - **kwargs + **kwargs, ) else: self.add_data_set( data=data[column].tolist(), series_type=series_type, name=column, - **kwargs + **kwargs, ) else: self.add_data_set(data, series_type) if ( - isinstance(data, pandas.Series) or - isinstance(data, pandas.DataFrame) and - series_type != 'scatter' + isinstance(data, pandas.Series) + or isinstance(data, pandas.DataFrame) + and series_type != "scatter" ): - self.set_options( - 'xAxis', - {'categories': data.index.values.tolist()} - ) + self.set_options("xAxis", {"categories": data.index.values.tolist()}) def __str__(self): self.buildhtml() return mark_safe(self.container) def media(self): - context = {'chart': self} + context = {"chart": self} renderer = get_default_renderer() return mark_safe(renderer.render(self.media_template, context)) diff --git a/utils/mail.py b/utils/mail.py index e86f01f..6d9a6f3 100644 --- a/utils/mail.py +++ b/utils/mail.py @@ -1,7 +1,12 @@ import logging from requests.exceptions import ConnectionError # pylint: disable=import-error -from exchangelib import Credentials, Account, Message, Mailbox # pylint: disable=import-error +from exchangelib import ( + Credentials, + Account, + Message, + Mailbox, +) # pylint: disable=import-error from exchangelib.errors import AutoDiscoverFailed # pylint: disable=import-error from wam.settings import WAM_EXCHANGE_ACCOUNT, WAM_EXCHANGE_EMAIL, WAM_EXCHANGE_PW @@ -24,37 +29,38 @@ def send_email(to_email, subject, message): Success status (True: successful) """ - credentials = Credentials(WAM_EXCHANGE_ACCOUNT, - WAM_EXCHANGE_PW) + credentials = Credentials(WAM_EXCHANGE_ACCOUNT, WAM_EXCHANGE_PW) try: - account = Account(WAM_EXCHANGE_EMAIL, - credentials=credentials, - autodiscover=True) + account = Account( + WAM_EXCHANGE_EMAIL, credentials=credentials, autodiscover=True + ) except ConnectionError: - err_msg = 'Feedback-Form - Verbindungsfehler!' + err_msg = "Feedback-Form - Verbindungsfehler!" logging.error(err_msg) return False except AutoDiscoverFailed: - err_msg = 'Feedback-Form - Konto- oder Authentifizierungsfehler!' + err_msg = "Feedback-Form - Konto- oder Authentifizierungsfehler!" logging.error(err_msg) return False except Exception as err: # pylint: disable=broad-except - err_msg = f'Feedback-Form - Sonstiger Fehler: {err}' + err_msg = f"Feedback-Form - Sonstiger Fehler: {err}" logging.error(err_msg) return False recipients = [Mailbox(email_address=to_email)] - m = Message(account=account, - folder=account.sent, - subject=subject, - body=message, - to_recipients=recipients) + m = Message( + account=account, + folder=account.sent, + subject=subject, + body=message, + to_recipients=recipients, + ) try: m.send_and_save() except Exception as err: # pylint: disable=broad-except - err_msg = f'Feedback-Form - Fehler beim Mailversand: {err}' + err_msg = f"Feedback-Form - Fehler beim Mailversand: {err}" logging.error(err_msg) return False diff --git a/utils/permissions.py b/utils/permissions.py index 2365d92..2f2f6bf 100644 --- a/utils/permissions.py +++ b/utils/permissions.py @@ -4,7 +4,7 @@ class GroupCheckMixin(UserPassesTestMixin): groups_to_check = [] - access_denied_url = 'access_denied' + access_denied_url = "access_denied" def get_login_url(self): # pylint: disable=R1705 @@ -15,7 +15,6 @@ def get_login_url(self): def test_func(self): if len(self.groups_to_check) == 0: - logging.warning( - 'No groups to check - Group check will always fail!') + logging.warning("No groups to check - Group check will always fail!") user_groups = {g.name for g in self.request.user.groups.all()} return len(user_groups.intersection(set(self.groups_to_check))) > 0 diff --git a/utils/shortcuts.py b/utils/shortcuts.py index ea64fbc..8d7a211 100644 --- a/utils/shortcuts.py +++ b/utils/shortcuts.py @@ -4,15 +4,14 @@ def redirect_with_get_parameters(url, **parameters): assert isinstance(parameters, dict) response = redirect(url) - response['Location'] += '?' + '&'.join( - [str(key) + '=' + str(value) for key, value in parameters.items()]) + response["Location"] += "?" + "&".join( + [str(key) + "=" + str(value) for key, value in parameters.items()] + ) return response def get_app_from_request(request): try: - return request.path.split('/')[1] + return request.path.split("/")[1] except AttributeError: - raise AttributeError( - 'Current app could not be found' - ) + raise AttributeError("Current app could not be found") diff --git a/utils/views.py b/utils/views.py index 5830d48..9361782 100644 --- a/utils/views.py +++ b/utils/views.py @@ -7,7 +7,12 @@ from django.core.validators import validate_email, ValidationError from django.shortcuts import redirect -from wam.settings import BASE_DIR, WAM_EXCHANGE_ACCOUNT, WAM_EXCHANGE_EMAIL, WAM_EXCHANGE_PW +from wam.settings import ( + BASE_DIR, + WAM_EXCHANGE_ACCOUNT, + WAM_EXCHANGE_EMAIL, + WAM_EXCHANGE_PW, +) from utils.forms import FeedbackForm from utils.mail import send_email @@ -17,14 +22,12 @@ class FeedbackView(FormView): # pylint: disable=too-many-ancestors app_name = None intro_text = None - template_name = 'feedback.html' + template_name = "feedback.html" form_class = FeedbackForm - success_url = '/feedback_thanks/' - error_url = '/feedback_error/' + success_url = "/feedback_thanks/" + error_url = "/feedback_error/" - def __init__(self, - app_name=None, - intro_text=None): + def __init__(self, app_name=None, intro_text=None): """ Parameters ---------- @@ -40,15 +43,16 @@ def __init__(self, self.app_name = app_name # read and validate app admin's mail address from app.cfg - app_config = ConfigObj(os.path.join(BASE_DIR, app_name, 'app.cfg')) - email = app_config.get('email', None) + app_config = ConfigObj(os.path.join(BASE_DIR, app_name, "app.cfg")) + email = app_config.get("email", None) if email is not None: try: validate_email(email) self.to_email = email except ValidationError: raise ValidationError( - f'E-mail address in {app_name}`s app.cfg is invalid!') + f"E-mail address in {app_name}`s app.cfg is invalid!" + ) else: raise ValueError('Parameter "app_name" must be specified!') @@ -58,72 +62,75 @@ def form_valid(self, form): form.submit() subject, body = self.prepare_message(**form.cleaned_data) - success = send_email(to_email=self.to_email, - subject=subject, - message=body) + success = send_email(to_email=self.to_email, subject=subject, message=body) if success: return super().form_valid(form) - return redirect('feedback_error', err_type='send') + return redirect("feedback_error", err_type="send") def get_context_data(self, **kwargs): context = super(FeedbackView, self).get_context_data(**kwargs) - context['app_name'] = self.app_name - context['intro_text'] = self.intro_text + context["app_name"] = self.app_name + context["intro_text"] = self.intro_text return context def get(self, request, *args, **kwargs): # check if env vars are set, if not redirect to error page - if WAM_EXCHANGE_ACCOUNT is None or \ - WAM_EXCHANGE_EMAIL is None or \ - WAM_EXCHANGE_PW is None: - err_msg = 'Feedback-Form - Konfigurationsfehler: ' \ - 'Umgebungsvariablen nicht gesetzt oder unvollständig!' + if ( + WAM_EXCHANGE_ACCOUNT is None + or WAM_EXCHANGE_EMAIL is None + or WAM_EXCHANGE_PW is None + ): + err_msg = ( + "Feedback-Form - Konfigurationsfehler: " + "Umgebungsvariablen nicht gesetzt oder unvollständig!" + ) logging.error(err_msg) - return redirect('feedback_error', err_type='config') + return redirect("feedback_error", err_type="config") return super().get(request, *args, **kwargs) def prepare_message(self, **kwargs): - subject = f'Nachricht über WAM Feedback-Formular: ' \ - f'App {self.app_name}' - body = f'Sie haben eine Nachricht über das Feedback-Formular der WAM ' \ - f'erhalten.\n\n' \ - f'App: {self.app_name}\n' \ - f'Absender: {kwargs.get("from_name", "")} ' \ - f'({kwargs.get("from_email", "")})\n' \ - f'Betreff: {kwargs.get("subject", "")}\n' \ - f'========================================\n' \ - f'{kwargs.get("message", "")}\n' \ - f'========================================\n' \ - f'Bitte antworte nicht auf diese E-Mail, junger PadaWAM!\n' \ - f'Gez. Obi WAM Kenobi' + subject = f"Nachricht über WAM Feedback-Formular: " f"App {self.app_name}" + body = ( + f"Sie haben eine Nachricht über das Feedback-Formular der WAM " + f"erhalten.\n\n" + f"App: {self.app_name}\n" + f'Absender: {kwargs.get("from_name", "")} ' + f'({kwargs.get("from_email", "")})\n' + f'Betreff: {kwargs.get("subject", "")}\n' + f"========================================\n" + f'{kwargs.get("message", "")}\n' + f"========================================\n" + f"Bitte antworte nicht auf diese E-Mail, junger PadaWAM!\n" + f"Gez. Obi WAM Kenobi" + ) return subject, body class FeedbackSuccessful(TemplateView): - template_name = 'feedback_successful.html' + template_name = "feedback_successful.html" class FeedbackError(TemplateView): """Error page for feedback form""" - template_name = 'feedback_error.html' + template_name = "feedback_error.html" def get(self, request, *args, **kwargs): context = self.get_context_data() # get error type and include corresponding message in context - err_type = kwargs.get('err_type') - if err_type == 'config': - error_text = 'Das Feedback-Formular ist nicht richtig konfiguriert.' - elif err_type == 'send': - error_text = 'Beim Senden ist leider ein Fehler aufgetreten.' + err_type = kwargs.get("err_type") + if err_type == "config": + error_text = "Das Feedback-Formular ist nicht richtig konfiguriert." + elif err_type == "send": + error_text = "Beim Senden ist leider ein Fehler aufgetreten." else: - error_text = 'Das Feedback-Formular funktioniert nicht richtig.' + error_text = "Das Feedback-Formular funktioniert nicht richtig." - context['error_text'] = error_text + context["error_text"] = error_text return self.render_to_response(context) diff --git a/utils/visualizations.py b/utils/visualizations.py index e466f6c..e3f1d83 100644 --- a/utils/visualizations.py +++ b/utils/visualizations.py @@ -1,4 +1,3 @@ - import itertools from abc import ABC, abstractmethod @@ -18,13 +17,13 @@ def __init__(self, data=None): def get_context(self, **kwargs): return { - 'div_id': kwargs.get('div_id', f'rank_{self.id}'), - 'div_kwargs': kwargs.get('div_kwargs', {}) + "div_id": kwargs.get("div_id", f"rank_{self.id}"), + "div_kwargs": kwargs.get("div_kwargs", {}), } def render(self, **kwargs): if self.template_name is None: - raise TemplateDoesNotExist('No template_name set') + raise TemplateDoesNotExist("No template_name set") renderer = get_default_renderer() context = self.get_context(**kwargs) return renderer.render(self.template_name, context) diff --git a/utils/widgets.py b/utils/widgets.py index beb6036..a0140d3 100644 --- a/utils/widgets.py +++ b/utils/widgets.py @@ -1,4 +1,3 @@ - import os from abc import ABC from typing import List, Tuple, Optional @@ -47,18 +46,18 @@ class InfoButton(CustomWidget): showing info text (markdown supported). """ - template_name = 'widgets/info_button.html' + template_name = "widgets/info_button.html" counter = count() def __init__( - self, - text: str = '', - tooltip: str = '', - is_markdown: bool = False, - ionicon_type: str = 'ion-information-circled', - ionicon_size: str = 'small', - ionicon_color: str = None, - info_id: str = None + self, + text: str = "", + tooltip: str = "", + is_markdown: bool = False, + ionicon_type: str = "ion-information-circled", + ionicon_size: str = "small", + ionicon_color: str = None, + info_id: str = None, ): """ @@ -83,9 +82,11 @@ def __init__( prepended by "info_". By default, a counter is used which results in ids "info_0", "info_1" etc. """ - self.text = markdown(text, - extensions=MARKDOWNX_MARKDOWN_EXTENSIONS)\ - if is_markdown else text + self.text = ( + markdown(text, extensions=MARKDOWNX_MARKDOWN_EXTENSIONS) + if is_markdown + else text + ) self.tooltip = tooltip self.ionicon_type = ionicon_type self.ionicon_size = ionicon_size @@ -98,12 +99,12 @@ def __init__( def get_context(self): return { - 'info_id': f'info_{self.id}', - 'text': self.text, - 'tooltip': self.tooltip, - 'ionicon_type': self.ionicon_type, - 'ionicon_size': self.ionicon_size, - 'ionicon_color': self.ionicon_color + "info_id": f"info_{self.id}", + "text": self.text, + "tooltip": self.tooltip, + "ionicon_type": self.ionicon_type, + "ionicon_size": self.ionicon_size, + "ionicon_color": self.ionicon_color, } @@ -114,13 +115,14 @@ class Wizard(CustomWidget): Active steps are hrefs, current step is circled, coming steps are inactive. Optionally, screen reader text for current step can be given. """ - template_name = 'widgets/wizard.html' + + template_name = "widgets/wizard.html" def __init__( - self, - urls: List[Optional[Tuple[str, str]]], - current: int, - screen_reader_for_current: str = None + self, + urls: List[Optional[Tuple[str, str]]], + current: int, + screen_reader_for_current: str = None, ): """ @@ -140,17 +142,18 @@ def __init__( def get_context(self): return { - 'urls': self.urls, - 'current': self.current, - 'screen_reader': self.screen_reader + "urls": self.urls, + "current": self.current, + "screen_reader": self.screen_reader, } class CSVWidget: """Reads in CSV-file and renders it as table""" - def __init__(self, filename, caption, csv_kwargs=None, html_kwargs=None, - links=None): + def __init__( + self, filename, caption, csv_kwargs=None, html_kwargs=None, links=None + ): """ Parameters ---------- @@ -175,9 +178,8 @@ def __init__(self, filename, caption, csv_kwargs=None, html_kwargs=None, csv_kwargs = {} if csv_kwargs is None else csv_kwargs self.data = pandas.read_csv(filename, **csv_kwargs) for link in links: - self.data[link] = self.data[link].apply( - lambda x: f'{x}') - self.data.fillna('-', inplace=True) + self.data[link] = self.data[link].apply(lambda x: f'{x}') + self.data.fillna("-", inplace=True) def __str__(self): style = self.data.style @@ -186,21 +188,18 @@ def __str__(self): class OrbitWidget(CustomWidget): - OrbitItem = namedtuple('OrbitItem', ['name', 'description', 'class_']) + OrbitItem = namedtuple("OrbitItem", ["name", "description", "class_"]) OrbitItem.__new__.__defaults__ = (None,) - template_name = 'widgets/orbit.html' - default_labels = { - 'next': 'Nächster', - 'previous': 'Vorheriger' - } + template_name = "widgets/orbit.html" + default_labels = {"next": "Nächster", "previous": "Vorheriger"} def __init__( - self, - caption: str, - orbits: List[OrbitItem], - labels: dict = None, - orbit_class='orbit' + self, + caption: str, + orbits: List[OrbitItem], + labels: dict = None, + orbit_class="orbit", ): if labels is None: labels = {} @@ -211,8 +210,8 @@ def __init__( def get_context(self): return { - 'caption': self.caption, - 'labels': self.labels, - 'orbits': self.orbits, - 'orbit_class': self.orbit_class + "caption": self.caption, + "labels": self.labels, + "orbits": self.orbits, + "orbit_class": self.orbit_class, } diff --git a/wam/admin.py b/wam/admin.py index ca9bc1d..fd3cbdd 100644 --- a/wam/admin.py +++ b/wam/admin.py @@ -1,4 +1,3 @@ - import importlib from django.contrib.admin import AdminSite @@ -11,8 +10,8 @@ class WamAdminSite(AdminSite): def get_urls(self): admin_urls = super(WamAdminSite, self).get_urls() - for app in settings.WAM_APPS + ['user_sessions']: - urls_module = importlib.import_module(f'{app}.urls') + for app in settings.WAM_APPS + ["user_sessions"]: + urls_module = importlib.import_module(f"{app}.urls") try: admin_urls.extend(urls_module.admin_url_patterns) except AttributeError: diff --git a/wam/celery.py b/wam/celery.py index cd2aa08..a7b6dc0 100644 --- a/wam/celery.py +++ b/wam/celery.py @@ -1,35 +1,28 @@ - from __future__ import absolute_import, unicode_literals import os from celery import Celery from wam.settings import config -os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'wam.settings') +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "wam.settings") -if 'READTHEDOCS' in os.environ: +if "READTHEDOCS" in os.environ: url = None else: - url = 'amqp://{user}:{password}@{host}:{port}/{vhost}'.format( - user=config['CELERY']['USER'], - password=config['CELERY']['PASSWORD'], - host=config['CELERY']['HOST'], - port=config['CELERY']['PORT'], - vhost=config['CELERY']['VHOST'], + url = "amqp://{user}:{password}@{host}:{port}/{vhost}".format( + user=config["CELERY"]["USER"], + password=config["CELERY"]["PASSWORD"], + host=config["CELERY"]["HOST"], + port=config["CELERY"]["PORT"], + vhost=config["CELERY"]["VHOST"], ) -app = Celery( - 'wam', - broker=url, - backend=url, -) +app = Celery("wam", broker=url, backend=url,) app.autodiscover_tasks() -app.conf.task_default_queue = 'wam_queue' +app.conf.task_default_queue = "wam_queue" # Optional configuration, see the application user guide. -app.conf.update( - result_expires=3600, -) +app.conf.update(result_expires=3600,) -if __name__ == '__main__': +if __name__ == "__main__": app.start() diff --git a/wam/settings.py b/wam/settings.py index a28ab18..5f17d9d 100644 --- a/wam/settings.py +++ b/wam/settings.py @@ -15,12 +15,12 @@ import logging from configobj import ConfigObj -config_file_path = os.environ.get('WAM_CONFIG_PATH') +config_file_path = os.environ.get("WAM_CONFIG_PATH") if config_file_path is None: raise KeyError( - 'Please add WAM_CONFIG_PATH as an environment variable, see ' - 'https://wam.readthedocs.io/en/latest/getting_started.html#environment-variables' + "Please add WAM_CONFIG_PATH as an environment variable, see " + "https://wam.readthedocs.io/en/latest/getting_started.html#environment-variables" ) config = ConfigObj(config_file_path) @@ -31,98 +31,93 @@ # See https://docs.djangoproject.com/en/1.11/howto/deployment/checklist/ # SECURITY WARNING: keep the secret key used in production secret! -SECRET_KEY = config['WAM']['SECRET_KEY'] +SECRET_KEY = config["WAM"]["SECRET_KEY"] # SECURITY WARNING: don't run with debug turned on in production! -DEBUG = config['WAM'].as_bool('DEBUG') +DEBUG = config["WAM"].as_bool("DEBUG") logging_level = logging.DEBUG if DEBUG else logging.INFO logging.getLogger().setLevel(logging_level) -ALLOWED_HOSTS = config['WAM'].as_list('ALLOWED_HOSTS') +ALLOWED_HOSTS = config["WAM"].as_list("ALLOWED_HOSTS") # Additional apps are loaded from environment variable -WAM_APPS = os.environ.get('WAM_APPS') +WAM_APPS = os.environ.get("WAM_APPS") if WAM_APPS is None: raise KeyError( - 'Please add WAM_APPS as an environment variable, see ' - 'https://wam.readthedocs.io/en/latest/getting_started.html#environment-variables' + "Please add WAM_APPS as an environment variable, see " + "https://wam.readthedocs.io/en/latest/getting_started.html#environment-variables" ) -if WAM_APPS == '': +if WAM_APPS == "": # The environment variable exists but no apps are defined WAM_APPS = [] else: # Apps name are retrieved - WAM_APPS = WAM_APPS.split(',') + WAM_APPS = WAM_APPS.split(",") APP_LABELS = { - app: ConfigObj(os.path.join(BASE_DIR, app, 'labels.cfg')) - for app in WAM_APPS + app: ConfigObj(os.path.join(BASE_DIR, app, "labels.cfg")) for app in WAM_APPS } # Application definition INSTALLED_APPS = WAM_APPS + [ - 'django.contrib.admin', - 'django.contrib.auth', - 'django.contrib.contenttypes', - 'django.contrib.sessions', - 'django.contrib.messages', - 'django.contrib.staticfiles', - 'django.contrib.gis', - 'django.forms', - 'crispy_forms', - 'meta', - 'user_sessions' + "django.contrib.admin", + "django.contrib.auth", + "django.contrib.contenttypes", + "django.contrib.sessions", + "django.contrib.messages", + "django.contrib.staticfiles", + "django.contrib.gis", + "django.forms", + "crispy_forms", + "meta", + "user_sessions", ] MIDDLEWARE = [ - 'django.middleware.security.SecurityMiddleware', - 'whitenoise.middleware.WhiteNoiseMiddleware', - 'django.contrib.sessions.middleware.SessionMiddleware', - 'django.middleware.locale.LocaleMiddleware', - 'django.middleware.common.CommonMiddleware', - 'django.middleware.csrf.CsrfViewMiddleware', - 'django.contrib.auth.middleware.AuthenticationMiddleware', - 'django.contrib.messages.middleware.MessageMiddleware', - 'django.middleware.clickjacking.XFrameOptionsMiddleware', + "django.middleware.security.SecurityMiddleware", + "whitenoise.middleware.WhiteNoiseMiddleware", + "django.contrib.sessions.middleware.SessionMiddleware", + "django.middleware.locale.LocaleMiddleware", + "django.middleware.common.CommonMiddleware", + "django.middleware.csrf.CsrfViewMiddleware", + "django.contrib.auth.middleware.AuthenticationMiddleware", + "django.contrib.messages.middleware.MessageMiddleware", + "django.middleware.clickjacking.XFrameOptionsMiddleware", ] -ROOT_URLCONF = 'wam.urls' +ROOT_URLCONF = "wam.urls" TEMPLATES = [ { - 'BACKEND': 'django.template.backends.django.DjangoTemplates', - 'DIRS': ['templates/'], - 'APP_DIRS': True, - 'OPTIONS': { - 'context_processors': [ - 'django.template.context_processors.debug', - 'django.template.context_processors.request', - 'django.contrib.auth.context_processors.auth', - 'django.contrib.messages.context_processors.messages', + "BACKEND": "django.template.backends.django.DjangoTemplates", + "DIRS": ["templates/"], + "APP_DIRS": True, + "OPTIONS": { + "context_processors": [ + "django.template.context_processors.debug", + "django.template.context_processors.request", + "django.contrib.auth.context_processors.auth", + "django.contrib.messages.context_processors.messages", ], - 'libraries': { - 'labels': 'templatetags.labels', - 'simple': 'templatetags.simple', + "libraries": { + "labels": "templatetags.labels", + "simple": "templatetags.simple", }, }, }, ] -FORM_RENDERER = 'django.forms.renderers.TemplatesSetting' +FORM_RENDERER = "django.forms.renderers.TemplatesSetting" -WSGI_APPLICATION = 'wam.wsgi.application' +WSGI_APPLICATION = "wam.wsgi.application" # Database # https://docs.djangoproject.com/en/1.11/ref/settings/#databases -DJANGO_DB = config['WAM'].get('DJANGO_DB', 'DEFAULT') -DATABASES = { - 'default': { - **config['DATABASES'][DJANGO_DB], - } -} +DJANGO_DB = config["WAM"].get("DJANGO_DB", "DEFAULT") +DATABASES = {"default": {**config["DATABASES"][DJANGO_DB],}} # Password validation @@ -130,25 +125,19 @@ AUTH_PASSWORD_VALIDATORS = [ { - 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', - }, - { - 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', - }, - { - 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', - }, - { - 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', + "NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator", }, + {"NAME": "django.contrib.auth.password_validation.MinimumLengthValidator",}, + {"NAME": "django.contrib.auth.password_validation.CommonPasswordValidator",}, + {"NAME": "django.contrib.auth.password_validation.NumericPasswordValidator",}, ] # Internationalization # https://docs.djangoproject.com/en/1.11/topics/i18n/ -LANGUAGE_CODE = 'de-DE' +LANGUAGE_CODE = "de-DE" -TIME_ZONE = 'Europe/Berlin' +TIME_ZONE = "Europe/Berlin" USE_I18N = True @@ -164,18 +153,16 @@ # Static files (CSS, JavaScript, Images) # https://docs.djangoproject.com/en/1.11/howto/static-files/ -STATIC_ROOT = os.path.join(BASE_DIR, 'staticfiles') -STATIC_URL = '/static/' -STATICFILES_DIRS = ( - [ - os.path.join(app, 'static') for app in WAM_APPS - if os.path.exists(os.path.join(app, 'static')) - ] + - [os.path.join(BASE_DIR, "static")] -) -STATICFILES_STORAGE = 'whitenoise.storage.CompressedStaticFilesStorage' +STATIC_ROOT = os.path.join(BASE_DIR, "staticfiles") +STATIC_URL = "/static/" +STATICFILES_DIRS = [ + os.path.join(app, "static") + for app in WAM_APPS + if os.path.exists(os.path.join(app, "static")) +] + [os.path.join(BASE_DIR, "static")] +STATICFILES_STORAGE = "whitenoise.storage.CompressedStaticFilesStorage" -SESSION_ENGINE = 'django.contrib.sessions.backends.db' +SESSION_ENGINE = "django.contrib.sessions.backends.db" # Contains data under session key: SESSION_DATA = None @@ -184,7 +171,7 @@ for app in WAM_APPS: # Import settings module from app and add constants to wam.settings try: - settings = importlib.import_module(f'{app}.settings', package='wam') + settings = importlib.import_module(f"{app}.settings", package="wam") except ModuleNotFoundError: pass else: @@ -200,16 +187,14 @@ # Import app_settings from app try: - importlib.import_module(f'{app}.app_settings', package='wam') + importlib.import_module(f"{app}.app_settings", package="wam") except ModuleNotFoundError: pass # E-mail config for outgoing mails (used by exchangelib) -WAM_EXCHANGE_ACCOUNT = config['WAM'].get('WAM_EXCHANGE_ACCOUNT') -WAM_EXCHANGE_EMAIL = config['WAM'].get('WAM_EXCHANGE_EMAIL') -WAM_EXCHANGE_PW = config['WAM'].get('WAM_EXCHANGE_PW') +WAM_EXCHANGE_ACCOUNT = config["WAM"].get("WAM_EXCHANGE_ACCOUNT") +WAM_EXCHANGE_EMAIL = config["WAM"].get("WAM_EXCHANGE_EMAIL") +WAM_EXCHANGE_PW = config["WAM"].get("WAM_EXCHANGE_PW") # Extensions for markdownx -MARKDOWNX_MARKDOWN_EXTENSIONS = [ - 'markdown.extensions.extra' -] +MARKDOWNX_MARKDOWN_EXTENSIONS = ["markdown.extensions.extra"] diff --git a/wam/urls.py b/wam/urls.py index c05b7d6..2cb8dbb 100644 --- a/wam/urls.py +++ b/wam/urls.py @@ -28,32 +28,34 @@ urlpatterns = [ - path('', views.IndexView.as_view(), name='index'), - path('contact/', views.ContactView.as_view(), name='contact'), - path('privacy/', views.PrivacyView.as_view(), name='privacy'), - path('impressum/', views.ImpressumView.as_view(), name='impressum'), - path('sources/', AppListView.as_view(model=models.Source), name='sources'), - path('assumptions/', AssumptionsView.as_view()), - path('admin/', wam_admin_site.urls), - path('accounts/login/', LoginView.as_view(template_name='login.html')), - path('access_denied/', TemplateView.as_view( - template_name='access_denied.html'), name='access_denied'), - path('feedback_thanks/', FeedbackSuccessful.as_view(), name='feedback_thanks'), - path('feedback_error//', FeedbackError.as_view(), name='feedback_error') + path("", views.IndexView.as_view(), name="index"), + path("contact/", views.ContactView.as_view(), name="contact"), + path("privacy/", views.PrivacyView.as_view(), name="privacy"), + path("impressum/", views.ImpressumView.as_view(), name="impressum"), + path("sources/", AppListView.as_view(model=models.Source), name="sources"), + path("assumptions/", AssumptionsView.as_view()), + path("admin/", wam_admin_site.urls), + path("accounts/login/", LoginView.as_view(template_name="login.html")), + path( + "access_denied/", + TemplateView.as_view(template_name="access_denied.html"), + name="access_denied", + ), + path("feedback_thanks/", FeedbackSuccessful.as_view(), name="feedback_thanks"), + path("feedback_error//", FeedbackError.as_view(), name="feedback_error"), ] try: - urlpatterns.append(re_path(r'^markdownx/', include('markdownx.urls'))) + urlpatterns.append(re_path(r"^markdownx/", include("markdownx.urls"))) except ImportError: pass for app_name in WAM_APPS: app_url = path( - '{}/'.format(app_name), - include(app_name + '.urls', namespace=app_name) + "{}/".format(app_name), include(app_name + ".urls", namespace=app_name) ) urlpatterns.append(app_url) # error handlers (work in non-debug mode only) -handler404 = 'wam.views.handler404' -handler500 = 'wam.views.handler500' +handler404 = "wam.views.handler404" +handler500 = "wam.views.handler500" diff --git a/wam/views.py b/wam/views.py index a06c31c..dca60c2 100644 --- a/wam/views.py +++ b/wam/views.py @@ -11,19 +11,19 @@ class ContactView(TemplateView): - template_name = 'contact.html' + template_name = "contact.html" class PrivacyView(TemplateView): - template_name = 'privacy_statement.html' + template_name = "privacy_statement.html" class ImpressumView(TemplateView): - template_name = 'impressum.html' + template_name = "impressum.html" class IndexView(TemplateView): - template_name = 'index.html' + template_name = "index.html" def get_context_data(self): context = super(IndexView, self).get_context_data() @@ -32,39 +32,35 @@ def get_context_data(self): for app_info in self.get_app_infos(app): app_infos[app_info.category].append(app_info) app_infos.default_factory = None - context['app_infos'] = app_infos + context["app_infos"] = app_infos return context @staticmethod def get_app_infos(app_name): # First, try to get app infos from app_settings.app_infos() function: try: - app_settings = importlib.import_module(f'{app_name}.app_settings') + app_settings = importlib.import_module(f"{app_name}.app_settings") except ImportError: pass else: - if hasattr(app_settings, 'app_infos'): + if hasattr(app_settings, "app_infos"): return app_settings.app_infos() # Second, try to read app.cfg: - app_config = ConfigObj(os.path.join(BASE_DIR, app_name, 'app.cfg')) + app_config = ConfigObj(os.path.join(BASE_DIR, app_name, "app.cfg")) if len(app_config) > 0: return [ AppInfo( - category=AppCategory(app_config['category']), - name=app_config['name'], - url=f'{app_name}:index', - closed=app_config.get('closed', False), - icon=app_config['icon'] + category=AppCategory(app_config["category"]), + name=app_config["name"], + url=f"{app_name}:index", + closed=app_config.get("closed", False), + icon=app_config["icon"], ) ] # Third, return generic app info: return [ - AppInfo( - category=AppCategory.App, - name=app_name, - url=f'{app_name}:index' - ) + AppInfo(category=AppCategory.App, name=app_name, url=f"{app_name}:index") ] def get(self, request, *args, **kwargs): @@ -72,17 +68,19 @@ def get(self, request, *args, **kwargs): return self.render_to_response(context) -def handler404(request, exception, template_name='error.html'): # pylint: disable=unused-argument +def handler404( + request, exception, template_name="error.html" +): # pylint: disable=unused-argument """A custom 404 page""" - context = {'err_text': 'Die Seite wurde leider nicht gefunden'} + context = {"err_text": "Die Seite wurde leider nicht gefunden"} response = render_to_response(template_name, context=context) response.status_code = 404 return response -def handler500(request, template_name='error.html'): # pylint: disable=unused-argument +def handler500(request, template_name="error.html"): # pylint: disable=unused-argument """A custom 500 page""" - context = {'err_text': 'Es ist ein Server-Fehler aufgetreten'} + context = {"err_text": "Es ist ein Server-Fehler aufgetreten"} response = render_to_response(template_name, context=context) response.status_code = 500 return response