From ef06ad583b6a8b436e140448af79ff0d321d9188 Mon Sep 17 00:00:00 2001 From: Marc Ducobu Date: Fri, 26 Mar 2021 13:50:49 +0100 Subject: [PATCH] Plugin generated via plugin builder --- .gitignore | 2 + Makefile | 244 +++++++++++++++++++++++++++ README.md | 34 ++++ __init__.py | 36 ++++ help/Makefile | 130 ++++++++++++++ help/source/conf.py | 216 ++++++++++++++++++++++++ help/source/index.rst | 20 +++ icon.png | Bin 0 -> 35802 bytes metadata.txt | 47 ++++++ pylintrc | 281 +++++++++++++++++++++++++++++++ resources.qrc | 5 + test/__init__.py | 2 + test/qgis_interface.py | 205 ++++++++++++++++++++++ test/tenbytenraster.asc | 19 +++ test/tenbytenraster.asc.aux.xml | 13 ++ test/tenbytenraster.keywords | 1 + test/tenbytenraster.lic | 18 ++ test/tenbytenraster.prj | 1 + test/tenbytenraster.qml | 26 +++ test/test_init.py | 64 +++++++ test/test_qgis_environment.py | 60 +++++++ test/test_resources.py | 44 +++++ test/test_translations.py | 55 ++++++ test/test_web_exporter_dialog.py | 55 ++++++ test/utilities.py | 61 +++++++ web_exporter.py | 200 ++++++++++++++++++++++ web_exporter_dialog.py | 44 +++++ web_exporter_dialog_base.ui | 67 ++++++++ 28 files changed, 1950 insertions(+) create mode 100644 .gitignore create mode 100644 Makefile create mode 100644 README.md create mode 100644 __init__.py create mode 100644 help/Makefile create mode 100644 help/source/conf.py create mode 100644 help/source/index.rst create mode 100644 icon.png create mode 100644 metadata.txt create mode 100644 pylintrc create mode 100644 resources.qrc create mode 100644 test/__init__.py create mode 100644 test/qgis_interface.py create mode 100644 test/tenbytenraster.asc create mode 100644 test/tenbytenraster.asc.aux.xml create mode 100644 test/tenbytenraster.keywords create mode 100644 test/tenbytenraster.lic create mode 100644 test/tenbytenraster.prj create mode 100644 test/tenbytenraster.qml create mode 100644 test/test_init.py create mode 100644 test/test_qgis_environment.py create mode 100644 test/test_resources.py create mode 100644 test/test_translations.py create mode 100644 test/test_web_exporter_dialog.py create mode 100644 test/utilities.py create mode 100644 web_exporter.py create mode 100644 web_exporter_dialog.py create mode 100644 web_exporter_dialog_base.ui diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..66331d7 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +resources.py +__pycache__ \ No newline at end of file diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..fbb7894 --- /dev/null +++ b/Makefile @@ -0,0 +1,244 @@ +#/*************************************************************************** +# WebExporter +# +# This plugin exports your vector layers on a remote web server +# ------------------- +# begin : 2021-03-26 +# git sha : $Format:%H$ +# copyright : (C) 2021 by Champs-Libres +# email : info@champs-libres.coop +# ***************************************************************************/ +# +#/*************************************************************************** +# * * +# * This program is free software; you can redistribute it and/or modify * +# * it under the terms of the GNU General Public License as published by * +# * the Free Software Foundation; either version 2 of the License, or * +# * (at your option) any later version. * +# * * +# ***************************************************************************/ + +################################################# +# Edit the following to match your sources lists +################################################# + + +#Add iso code for any locales you want to support here (space separated) +# default is no locales +# LOCALES = af +LOCALES = + +# If locales are enabled, set the name of the lrelease binary on your system. If +# you have trouble compiling the translations, you may have to specify the full path to +# lrelease +#LRELEASE = lrelease +#LRELEASE = lrelease-qt4 + + +# translation +SOURCES = \ + __init__.py \ + web_exporter.py web_exporter_dialog.py + +PLUGINNAME = web_exporter + +PY_FILES = \ + __init__.py \ + web_exporter.py web_exporter_dialog.py + +UI_FILES = web_exporter_dialog_base.ui + +EXTRAS = metadata.txt icon.png + +EXTRA_DIRS = + +COMPILED_RESOURCE_FILES = resources.py + +PEP8EXCLUDE=pydev,resources.py,conf.py,third_party,ui + +# QGISDIR points to the location where your plugin should be installed. +# This varies by platform, relative to your HOME directory: +# * Linux: +# .local/share/QGIS/QGIS3/profiles/default/python/plugins/ +# * Mac OS X: +# Library/Application Support/QGIS/QGIS3/profiles/default/python/plugins +# * Windows: +# AppData\Roaming\QGIS\QGIS3\profiles\default\python\plugins' + +QGISDIR=/home/marcu/.local/share/QGIS/QGIS3/profiles/default/python/plugins/ + +################################################# +# Normally you would not need to edit below here +################################################# + +HELP = help/build/html + +PLUGIN_UPLOAD = $(c)/plugin_upload.py + +RESOURCE_SRC=$(shell grep '^ *@@g;s/.*>//g' | tr '\n' ' ') + +.PHONY: default +default: + @echo While you can use make to build and deploy your plugin, pb_tool + @echo is a much better solution. + @echo A Python script, pb_tool provides platform independent management of + @echo your plugins and runs anywhere. + @echo You can install pb_tool using: pip install pb_tool + @echo See https://g-sherman.github.io/plugin_build_tool/ for info. + +compile: $(COMPILED_RESOURCE_FILES) + +%.py : %.qrc $(RESOURCES_SRC) + pyrcc5 -o $*.py $< + +%.qm : %.ts + $(LRELEASE) $< + +test: compile transcompile + @echo + @echo "----------------------" + @echo "Regression Test Suite" + @echo "----------------------" + + @# Preceding dash means that make will continue in case of errors + @-export PYTHONPATH=`pwd`:$(PYTHONPATH); \ + export QGIS_DEBUG=0; \ + export QGIS_LOG_FILE=/dev/null; \ + nosetests -v --with-id --with-coverage --cover-package=. \ + 3>&1 1>&2 2>&3 3>&- || true + @echo "----------------------" + @echo "If you get a 'no module named qgis.core error, try sourcing" + @echo "the helper script we have provided first then run make test." + @echo "e.g. source run-env-linux.sh ; make test" + @echo "----------------------" + +deploy: compile doc transcompile + @echo + @echo "------------------------------------------" + @echo "Deploying plugin to your .qgis2 directory." + @echo "------------------------------------------" + # The deploy target only works on unix like operating system where + # the Python plugin directory is located at: + # $HOME/$(QGISDIR)/python/plugins + mkdir -p $(HOME)/$(QGISDIR)/python/plugins/$(PLUGINNAME) + cp -vf $(PY_FILES) $(HOME)/$(QGISDIR)/python/plugins/$(PLUGINNAME) + cp -vf $(UI_FILES) $(HOME)/$(QGISDIR)/python/plugins/$(PLUGINNAME) + cp -vf $(COMPILED_RESOURCE_FILES) $(HOME)/$(QGISDIR)/python/plugins/$(PLUGINNAME) + cp -vf $(EXTRAS) $(HOME)/$(QGISDIR)/python/plugins/$(PLUGINNAME) + cp -vfr i18n $(HOME)/$(QGISDIR)/python/plugins/$(PLUGINNAME) + cp -vfr $(HELP) $(HOME)/$(QGISDIR)/python/plugins/$(PLUGINNAME)/help + # Copy extra directories if any + (foreach EXTRA_DIR,(EXTRA_DIRS), cp -R (EXTRA_DIR) (HOME)/(QGISDIR)/python/plugins/(PLUGINNAME)/;) + + +# The dclean target removes compiled python files from plugin directory +# also deletes any .git entry +dclean: + @echo + @echo "-----------------------------------" + @echo "Removing any compiled python files." + @echo "-----------------------------------" + find $(HOME)/$(QGISDIR)/python/plugins/$(PLUGINNAME) -iname "*.pyc" -delete + find $(HOME)/$(QGISDIR)/python/plugins/$(PLUGINNAME) -iname ".git" -prune -exec rm -Rf {} \; + + +derase: + @echo + @echo "-------------------------" + @echo "Removing deployed plugin." + @echo "-------------------------" + rm -Rf $(HOME)/$(QGISDIR)/python/plugins/$(PLUGINNAME) + +zip: deploy dclean + @echo + @echo "---------------------------" + @echo "Creating plugin zip bundle." + @echo "---------------------------" + # The zip target deploys the plugin and creates a zip file with the deployed + # content. You can then upload the zip file on http://plugins.qgis.org + rm -f $(PLUGINNAME).zip + cd $(HOME)/$(QGISDIR)/python/plugins; zip -9r $(CURDIR)/$(PLUGINNAME).zip $(PLUGINNAME) + +package: compile + # Create a zip package of the plugin named $(PLUGINNAME).zip. + # This requires use of git (your plugin development directory must be a + # git repository). + # To use, pass a valid commit or tag as follows: + # make package VERSION=Version_0.3.2 + @echo + @echo "------------------------------------" + @echo "Exporting plugin to zip package. " + @echo "------------------------------------" + rm -f $(PLUGINNAME).zip + git archive --prefix=$(PLUGINNAME)/ -o $(PLUGINNAME).zip $(VERSION) + echo "Created package: $(PLUGINNAME).zip" + +upload: zip + @echo + @echo "-------------------------------------" + @echo "Uploading plugin to QGIS Plugin repo." + @echo "-------------------------------------" + $(PLUGIN_UPLOAD) $(PLUGINNAME).zip + +transup: + @echo + @echo "------------------------------------------------" + @echo "Updating translation files with any new strings." + @echo "------------------------------------------------" + @chmod +x scripts/update-strings.sh + @scripts/update-strings.sh $(LOCALES) + +transcompile: + @echo + @echo "----------------------------------------" + @echo "Compiled translation files to .qm files." + @echo "----------------------------------------" + @chmod +x scripts/compile-strings.sh + @scripts/compile-strings.sh $(LRELEASE) $(LOCALES) + +transclean: + @echo + @echo "------------------------------------" + @echo "Removing compiled translation files." + @echo "------------------------------------" + rm -f i18n/*.qm + +clean: + @echo + @echo "------------------------------------" + @echo "Removing uic and rcc generated files" + @echo "------------------------------------" + rm $(COMPILED_UI_FILES) $(COMPILED_RESOURCE_FILES) + +doc: + @echo + @echo "------------------------------------" + @echo "Building documentation using sphinx." + @echo "------------------------------------" + cd help; make html + +pylint: + @echo + @echo "-----------------" + @echo "Pylint violations" + @echo "-----------------" + @pylint --reports=n --rcfile=pylintrc . || true + @echo + @echo "----------------------" + @echo "If you get a 'no module named qgis.core' error, try sourcing" + @echo "the helper script we have provided first then run make pylint." + @echo "e.g. source run-env-linux.sh ; make pylint" + @echo "----------------------" + + +# Run pep8 style checking +#http://pypi.python.org/pypi/pep8 +pep8: + @echo + @echo "-----------" + @echo "PEP8 issues" + @echo "-----------" + @pep8 --repeat --ignore=E203,E121,E122,E123,E124,E125,E126,E127,E128 --exclude $(PEP8EXCLUDE) . || true + @echo "-----------" + @echo "Ignored in PEP8 check:" + @echo $(PEP8EXCLUDE) diff --git a/README.md b/README.md new file mode 100644 index 0000000..a1425e4 --- /dev/null +++ b/README.md @@ -0,0 +1,34 @@ + +## How to install the plugin for development ? + +1. Make a symbolic link in the qgis python repository (on linux +this reporitoy is ~/.local/share/QGIS/QGIS3/profiles/default/python/plugins ). + +`` +$ ln -s $(pwd) ~/.local/share/QGIS/QGIS3/profiles/default/python/plugins +`` + +2. Create the file `resources.py` + + +`` +$ pyrcc5 resources.qrc -o resources.py +`` + +That's it ! You can restart QGIS and enable the plugin using the plugin manager. + + +What's Next: + + * Run the tests (``make test``) + + * Test the plugin by enabling it in the QGIS plugin manager + + * Customize it by editing the implementation file: ``web_exporter.py`` + + * Create your own custom icon, replacing the default icon.png + + * Modify your user interface by opening WebExporter_dialog_base.ui in Qt Designer + + * You can use the Makefile to compile your Ui and resource files when + you make changes. This requires GNU make (gmake) \ No newline at end of file diff --git a/__init__.py b/__init__.py new file mode 100644 index 0000000..bc1978a --- /dev/null +++ b/__init__.py @@ -0,0 +1,36 @@ +# -*- coding: utf-8 -*- +""" +/*************************************************************************** + WebExporter + A QGIS plugin + This plugin exports your vector layers on a remote web server + Generated by Plugin Builder: http://g-sherman.github.io/Qgis-Plugin-Builder/ + ------------------- + begin : 2021-03-26 + copyright : (C) 2021 by Champs-Libres + email : info@champs-libres.coop + git sha : $Format:%H$ + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + This script initializes the plugin, making it known to QGIS. +""" + + +# noinspection PyPep8Naming +def classFactory(iface): # pylint: disable=invalid-name + """Load WebExporter class from file WebExporter. + + :param iface: A QGIS interface instance. + :type iface: QgsInterface + """ + # + from .web_exporter import WebExporter + return WebExporter(iface) diff --git a/help/Makefile b/help/Makefile new file mode 100644 index 0000000..9def777 --- /dev/null +++ b/help/Makefile @@ -0,0 +1,130 @@ +# Makefile for Sphinx documentation +# + +# You can set these variables from the command line. +SPHINXOPTS = +SPHINXBUILD = sphinx-build +PAPER = +BUILDDIR = build + +# Internal variables. +PAPEROPT_a4 = -D latex_paper_size=a4 +PAPEROPT_letter = -D latex_paper_size=letter +ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source + +.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest + +help: + @echo "Please use \`make ' where is one of" + @echo " html to make standalone HTML files" + @echo " dirhtml to make HTML files named index.html in directories" + @echo " singlehtml to make a single large HTML file" + @echo " pickle to make pickle files" + @echo " json to make JSON files" + @echo " htmlhelp to make HTML files and a HTML help project" + @echo " qthelp to make HTML files and a qthelp project" + @echo " devhelp to make HTML files and a Devhelp project" + @echo " epub to make an epub" + @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" + @echo " latexpdf to make LaTeX files and run them through pdflatex" + @echo " text to make text files" + @echo " man to make manual pages" + @echo " changes to make an overview of all changed/added/deprecated items" + @echo " linkcheck to check all external links for integrity" + @echo " doctest to run all doctests embedded in the documentation (if enabled)" + +clean: + -rm -rf $(BUILDDIR)/* + +html: + $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html + @echo + @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." + +dirhtml: + $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml + @echo + @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." + +singlehtml: + $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml + @echo + @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." + +pickle: + $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle + @echo + @echo "Build finished; now you can process the pickle files." + +json: + $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json + @echo + @echo "Build finished; now you can process the JSON files." + +htmlhelp: + $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp + @echo + @echo "Build finished; now you can run HTML Help Workshop with the" \ + ".hhp project file in $(BUILDDIR)/htmlhelp." + +qthelp: + $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp + @echo + @echo "Build finished; now you can run "qcollectiongenerator" with the" \ + ".qhcp project file in $(BUILDDIR)/qthelp, like this:" + @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/template_class.qhcp" + @echo "To view the help file:" + @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/template_class.qhc" + +devhelp: + $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp + @echo + @echo "Build finished." + @echo "To view the help file:" + @echo "# mkdir -p $$HOME/.local/share/devhelp/template_class" + @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/template_class" + @echo "# devhelp" + +epub: + $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub + @echo + @echo "Build finished. The epub file is in $(BUILDDIR)/epub." + +latex: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo + @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." + @echo "Run \`make' in that directory to run these through (pdf)latex" \ + "(use \`make latexpdf' here to do that automatically)." + +latexpdf: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo "Running LaTeX files through pdflatex..." + make -C $(BUILDDIR)/latex all-pdf + @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." + +text: + $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text + @echo + @echo "Build finished. The text files are in $(BUILDDIR)/text." + +man: + $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man + @echo + @echo "Build finished. The manual pages are in $(BUILDDIR)/man." + +changes: + $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes + @echo + @echo "The overview file is in $(BUILDDIR)/changes." + +linkcheck: + $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck + @echo + @echo "Link check complete; look for any errors in the above output " \ + "or in $(BUILDDIR)/linkcheck/output.txt." + +doctest: + $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest + @echo "Testing of doctests in the sources finished, look at the " \ + "results in $(BUILDDIR)/doctest/output.txt." diff --git a/help/source/conf.py b/help/source/conf.py new file mode 100644 index 0000000..8c769a1 --- /dev/null +++ b/help/source/conf.py @@ -0,0 +1,216 @@ +# -*- coding: utf-8 -*- +# +# WebExporter documentation build configuration file, created by +# sphinx-quickstart on Sun Feb 12 17:11:03 2012. +# +# This file is execfile()d with the current directory set to its containing dir. +# +# Note that not all possible configuration values are present in this +# autogenerated file. +# +# All configuration values have a default; values that are commented out +# serve to show the default. + +import sys, os + +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. If the directory is relative to the +# documentation root, use os.path.abspath to make it absolute, like shown here. +#sys.path.insert(0, os.path.abspath('.')) + +# -- General configuration ----------------------------------------------------- + +# If your documentation needs a minimal Sphinx version, state it here. +#needs_sphinx = '1.0' + +# Add any Sphinx extension module names here, as strings. They can be extensions +# coming with Sphinx (named 'sphinx.ext.*') or your custom ones. +extensions = ['sphinx.ext.todo', 'sphinx.ext.imgmath', 'sphinx.ext.viewcode'] + +# Add any paths that contain templates here, relative to this directory. +templates_path = ['_templates'] + +# The suffix of source filenames. +source_suffix = '.rst' + +# The encoding of source files. +#source_encoding = 'utf-8-sig' + +# The master toctree document. +master_doc = 'index' + +# General information about the project. +project = u'WebExporter' +copyright = u'2013, Champs-Libres' + +# The version info for the project you're documenting, acts as replacement for +# |version| and |release|, also used in various other places throughout the +# built documents. +# +# The short X.Y version. +version = '0.1' +# The full version, including alpha/beta/rc tags. +release = '0.1' + +# The language for content autogenerated by Sphinx. Refer to documentation +# for a list of supported languages. +#language = None + +# There are two options for replacing |today|: either, you set today to some +# non-false value, then it is used: +#today = '' +# Else, today_fmt is used as the format for a strftime call. +#today_fmt = '%B %d, %Y' + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +exclude_patterns = [] + +# The reST default role (used for this markup: `text`) to use for all documents. +#default_role = None + +# If true, '()' will be appended to :func: etc. cross-reference text. +#add_function_parentheses = True + +# If true, the current module name will be prepended to all description +# unit titles (such as .. function::). +#add_TemplateModuleNames = True + +# If true, sectionauthor and moduleauthor directives will be shown in the +# output. They are ignored by default. +#show_authors = False + +# The name of the Pygments (syntax highlighting) style to use. +pygments_style = 'sphinx' + +# A list of ignored prefixes for module index sorting. +#modindex_common_prefix = [] + + +# -- Options for HTML output --------------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +html_theme = 'default' + +# 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 +# documentation. +#html_theme_options = {} + +# Add any paths that contain custom themes here, relative to this directory. +#html_theme_path = [] + +# The name for this set of Sphinx documents. If None, it defaults to +# " v documentation". +#html_title = None + +# A shorter title for the navigation bar. Default is the same as html_title. +#html_short_title = None + +# The name of an image file (relative to this directory) to place at the top +# of the sidebar. +#html_logo = None + +# The name of an image file (within the static path) to use as favicon of the +# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 +# pixels large. +#html_favicon = None + +# 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'] + +# If not '', a 'Last updated on:' timestamp is inserted at every page bottom, +# using the given strftime format. +#html_last_updated_fmt = '%b %d, %Y' + +# If true, SmartyPants will be used to convert quotes and dashes to +# typographically correct entities. +#html_use_smartypants = True + +# Custom sidebar templates, maps document names to template names. +#html_sidebars = {} + +# Additional templates that should be rendered to pages, maps page names to +# template names. +#html_additional_pages = {} + +# If false, no module index is generated. +#html_domain_indices = True + +# If false, no index is generated. +#html_use_index = True + +# If true, the index is split into individual pages for each letter. +#html_split_index = False + +# If true, links to the reST sources are added to the pages. +#html_show_sourcelink = True + +# If true, "Created using Sphinx" is shown in the HTML footer. Default is True. +#html_show_sphinx = True + +# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. +#html_show_copyright = True + +# If true, an OpenSearch description file will be output, and all pages will +# contain a tag referring to it. The value of this option must be the +# base URL from which the finished HTML is served. +#html_use_opensearch = '' + +# This is the file name suffix for HTML files (e.g. ".xhtml"). +#html_file_suffix = None + +# Output file base name for HTML help builder. +htmlhelp_basename = 'TemplateClassdoc' + + +# -- Options for LaTeX output -------------------------------------------------- + +# The paper size ('letter' or 'a4'). +#latex_paper_size = 'letter' + +# The font size ('10pt', '11pt' or '12pt'). +#latex_font_size = '10pt' + +# Grouping the document tree into LaTeX files. List of tuples +# (source start file, target name, title, author, documentclass [howto/manual]). +latex_documents = [ + ('index', 'WebExporter.tex', u'WebExporter Documentation', + u'Champs-Libres', 'manual'), +] + +# The name of an image file (relative to this directory) to place at the top of +# the title page. +#latex_logo = None + +# For "manual" documents, if this is true, then toplevel headings are parts, +# not chapters. +#latex_use_parts = False + +# If true, show page references after internal links. +#latex_show_pagerefs = False + +# If true, show URL addresses after external links. +#latex_show_urls = False + +# Additional stuff for the LaTeX preamble. +#latex_preamble = '' + +# Documents to append as an appendix to all manuals. +#latex_appendices = [] + +# If false, no module index is generated. +#latex_domain_indices = True + + +# -- Options for manual page output -------------------------------------------- + +# One entry per manual page. List of tuples +# (source start file, name, description, authors, manual section). +man_pages = [ + ('index', 'TemplateClass', u'WebExporter Documentation', + [u'Champs-Libres'], 1) +] diff --git a/help/source/index.rst b/help/source/index.rst new file mode 100644 index 0000000..f1ad908 --- /dev/null +++ b/help/source/index.rst @@ -0,0 +1,20 @@ +.. WebExporter documentation master file, created by + sphinx-quickstart on Sun Feb 12 17:11:03 2012. + You can adapt this file completely to your liking, but it should at least + contain the root `toctree` directive. + +Welcome to WebExporter's documentation! +============================================ + +Contents: + +.. toctree:: + :maxdepth: 2 + +Indices and tables +================== + +* :ref:`genindex` +* :ref:`modindex` +* :ref:`search` + diff --git a/icon.png b/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..18dcdd480b4314f196706abe38879e4c0327b48c GIT binary patch literal 35802 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 literal 0 HcmV?d00001 diff --git a/metadata.txt b/metadata.txt new file mode 100644 index 0000000..6516e56 --- /dev/null +++ b/metadata.txt @@ -0,0 +1,47 @@ +# This file contains metadata for your plugin. + +# This file should be included when you package your plugin.# Mandatory items: + +[general] +name=Web Exporter +qgisMinimumVersion=3.0 +description=This plugin exports your vector layers on a remote web server +version=0.1 +author=Champs-Libres +email=info@champs-libres.coop + +about=Provide a brief description of the plugin and its purpose. + +tracker=http://bugs +repository=http://repo +# End of mandatory metadata + +# Recommended items: + +hasProcessingProvider=no +# Uncomment the following line and add your changelog: +# changelog= + +# Tags are comma separated with spaces allowed +tags=python + +homepage=http://homepage +category=Vector +icon=icon.png +# experimental flag +experimental=True + +# deprecated flag (applies to the whole plugin, not just a single version) +deprecated=False + +# Since QGIS 3.8, a comma separated list of plugins to be installed +# (or upgraded) can be specified. +# Check the documentation for more information. +# plugin_dependencies= + +Category of the plugin: Raster, Vector, Database or Web +# category= + +# If the plugin can run on QGIS Server. +server=False + diff --git a/pylintrc b/pylintrc new file mode 100644 index 0000000..7e168f6 --- /dev/null +++ b/pylintrc @@ -0,0 +1,281 @@ +[MASTER] + +# Specify a configuration file. +#rcfile= + +# Python code to execute, usually for sys.path manipulation such as +# pygtk.require(). +#init-hook= + +# Profiled execution. +profile=no + +# Add files or directories to the blacklist. They should be base names, not +# paths. +ignore=CVS + +# Pickle collected data for later comparisons. +persistent=yes + +# List of plugins (as comma separated values of python modules names) to load, +# usually to register additional checkers. +load-plugins= + + +[MESSAGES CONTROL] + +# Enable the message, report, category or checker with the given id(s). You can +# either give multiple identifier separated by comma (,) or put this option +# multiple time. See also the "--disable" option for examples. +#enable= + +# Disable the message, report, category or checker with the given id(s). You +# can either give multiple identifiers separated by comma (,) or put this +# option multiple times (only on the command line, not in the configuration +# file where it should appear only once).You can also use "--disable=all" to +# disable everything first and then reenable specific checks. For example, if +# you want to run only the similarities checker, you can use "--disable=all +# --enable=similarities". If you want to run only the classes checker, but have +# no Warning level messages displayed, use"--disable=all --enable=classes +# --disable=W" +# see http://stackoverflow.com/questions/21487025/pylint-locally-defined-disables-still-give-warnings-how-to-suppress-them +disable=locally-disabled,C0103 + + +[REPORTS] + +# Set the output format. Available formats are text, parseable, colorized, msvs +# (visual studio) and html. You can also give a reporter class, eg +# mypackage.mymodule.MyReporterClass. +output-format=text + +# Put messages in a separate file for each module / package specified on the +# command line instead of printing them on stdout. Reports (if any) will be +# written in a file name "pylint_global.[txt|html]". +files-output=no + +# Tells whether to display a full report or only the messages +reports=yes + +# Python expression which should return a note less than 10 (10 is the highest +# note). You have access to the variables errors warning, statement which +# respectively contain the number of errors / warnings messages and the total +# number of statements analyzed. This is used by the global evaluation report +# (RP0004). +evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10) + +# Add a comment according to your evaluation note. This is used by the global +# evaluation report (RP0004). +comment=no + +# Template used to display messages. This is a python new-style format string +# used to format the message information. See doc for all details +#msg-template= + + +[BASIC] + +# Required attributes for module, separated by a comma +required-attributes= + +# List of builtins function names that should not be used, separated by a comma +bad-functions=map,filter,apply,input + +# Regular expression which should only match correct module names +module-rgx=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$ + +# Regular expression which should only match correct module level names +const-rgx=(([A-Z_][A-Z0-9_]*)|(__.*__))$ + +# Regular expression which should only match correct class names +class-rgx=[A-Z_][a-zA-Z0-9]+$ + +# Regular expression which should only match correct function names +function-rgx=[a-z_][a-z0-9_]{2,30}$ + +# Regular expression which should only match correct method names +method-rgx=[a-z_][a-z0-9_]{2,30}$ + +# Regular expression which should only match correct instance attribute names +attr-rgx=[a-z_][a-z0-9_]{2,30}$ + +# Regular expression which should only match correct argument names +argument-rgx=[a-z_][a-z0-9_]{2,30}$ + +# Regular expression which should only match correct variable names +variable-rgx=[a-z_][a-z0-9_]{2,30}$ + +# Regular expression which should only match correct attribute names in class +# bodies +class-attribute-rgx=([A-Za-z_][A-Za-z0-9_]{2,30}|(__.*__))$ + +# Regular expression which should only match correct list comprehension / +# generator expression variable names +inlinevar-rgx=[A-Za-z_][A-Za-z0-9_]*$ + +# Good variable names which should always be accepted, separated by a comma +good-names=i,j,k,ex,Run,_ + +# Bad variable names which should always be refused, separated by a comma +bad-names=foo,bar,baz,toto,tutu,tata + +# Regular expression which should only match function or class names that do +# not require a docstring. +no-docstring-rgx=__.*__ + +# Minimum line length for functions/classes that require docstrings, shorter +# ones are exempt. +docstring-min-length=-1 + + +[MISCELLANEOUS] + +# List of note tags to take in consideration, separated by a comma. +notes=FIXME,XXX,TODO + + +[TYPECHECK] + +# Tells whether missing members accessed in mixin class should be ignored. A +# mixin class is detected if its name ends with "mixin" (case insensitive). +ignore-mixin-members=yes + +# List of classes names for which member attributes should not be checked +# (useful for classes with attributes dynamically set). +ignored-classes=SQLObject + +# When zope mode is activated, add a predefined set of Zope acquired attributes +# to generated-members. +zope=no + +# List of members which are set dynamically and missed by pylint inference +# system, and so shouldn't trigger E0201 when accessed. Python regular +# expressions are accepted. +generated-members=REQUEST,acl_users,aq_parent + + +[VARIABLES] + +# Tells whether we should check for unused import in __init__ files. +init-import=no + +# A regular expression matching the beginning of the name of dummy variables +# (i.e. not used). +dummy-variables-rgx=_$|dummy + +# List of additional names supposed to be defined in builtins. Remember that +# you should avoid to define new builtins when possible. +additional-builtins= + + +[FORMAT] + +# Maximum number of characters on a single line. +max-line-length=80 + +# Regexp for a line that is allowed to be longer than the limit. +ignore-long-lines=^\s*(# )??$ + +# Allow the body of an if to be on the same line as the test if there is no +# else. +single-line-if-stmt=no + +# List of optional constructs for which whitespace checking is disabled +no-space-check=trailing-comma,dict-separator + +# Maximum number of lines in a module +max-module-lines=1000 + +# String used as indentation unit. This is usually " " (4 spaces) or "\t" (1 +# tab). +indent-string=' ' + + +[SIMILARITIES] + +# Minimum lines number of a similarity. +min-similarity-lines=4 + +# Ignore comments when computing similarities. +ignore-comments=yes + +# Ignore docstrings when computing similarities. +ignore-docstrings=yes + +# Ignore imports when computing similarities. +ignore-imports=no + + +[IMPORTS] + +# Deprecated modules which should not be used, separated by a comma +deprecated-modules=regsub,TERMIOS,Bastion,rexec + +# Create a graph of every (i.e. internal and external) dependencies in the +# given file (report RP0402 must not be disabled) +import-graph= + +# Create a graph of external dependencies in the given file (report RP0402 must +# not be disabled) +ext-import-graph= + +# Create a graph of internal dependencies in the given file (report RP0402 must +# not be disabled) +int-import-graph= + + +[DESIGN] + +# Maximum number of arguments for function / method +max-args=5 + +# Argument names that match this expression will be ignored. Default to name +# with leading underscore +ignored-argument-names=_.* + +# Maximum number of locals for function / method body +max-locals=15 + +# Maximum number of return / yield for function / method body +max-returns=6 + +# Maximum number of branch for function / method body +max-branches=12 + +# Maximum number of statements in function / method body +max-statements=50 + +# Maximum number of parents for a class (see R0901). +max-parents=7 + +# Maximum number of attributes for a class (see R0902). +max-attributes=7 + +# Minimum number of public methods for a class (see R0903). +min-public-methods=2 + +# Maximum number of public methods for a class (see R0904). +max-public-methods=20 + + +[CLASSES] + +# List of interface methods to ignore, separated by a comma. This is used for +# instance to not check methods defines in Zope's Interface base class. +ignore-iface-methods=isImplementedBy,deferred,extends,names,namesAndDescriptions,queryDescriptionFor,getBases,getDescriptionFor,getDoc,getName,getTaggedValue,getTaggedValueTags,isEqualOrExtendedBy,setTaggedValue,isImplementedByInstancesOf,adaptWith,is_implemented_by + +# List of method names used to declare (i.e. assign) instance attributes. +defining-attr-methods=__init__,__new__,setUp + +# List of valid names for the first argument in a class method. +valid-classmethod-first-arg=cls + +# List of valid names for the first argument in a metaclass class method. +valid-metaclass-classmethod-first-arg=mcs + + +[EXCEPTIONS] + +# Exceptions that will emit a warning when being caught. Defaults to +# "Exception" +overgeneral-exceptions=Exception diff --git a/resources.qrc b/resources.qrc new file mode 100644 index 0000000..c397531 --- /dev/null +++ b/resources.qrc @@ -0,0 +1,5 @@ + + + icon.png + + diff --git a/test/__init__.py b/test/__init__.py new file mode 100644 index 0000000..8feeb0b --- /dev/null +++ b/test/__init__.py @@ -0,0 +1,2 @@ +# import qgis libs so that ve set the correct sip api version +import qgis # pylint: disable=W0611 # NOQA \ No newline at end of file diff --git a/test/qgis_interface.py b/test/qgis_interface.py new file mode 100644 index 0000000..a407052 --- /dev/null +++ b/test/qgis_interface.py @@ -0,0 +1,205 @@ +# coding=utf-8 +"""QGIS plugin implementation. + +.. note:: This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + +.. note:: This source code was copied from the 'postgis viewer' application + with original authors: + Copyright (c) 2010 by Ivan Mincik, ivan.mincik@gista.sk + Copyright (c) 2011 German Carrillo, geotux_tuxman@linuxmail.org + Copyright (c) 2014 Tim Sutton, tim@linfiniti.com + +""" + +__author__ = 'tim@linfiniti.com' +__revision__ = '$Format:%H$' +__date__ = '10/01/2011' +__copyright__ = ( + 'Copyright (c) 2010 by Ivan Mincik, ivan.mincik@gista.sk and ' + 'Copyright (c) 2011 German Carrillo, geotux_tuxman@linuxmail.org' + 'Copyright (c) 2014 Tim Sutton, tim@linfiniti.com' +) + +import logging +from qgis.PyQt.QtCore import QObject, pyqtSlot, pyqtSignal +from qgis.core import QgsMapLayerRegistry +from qgis.gui import QgsMapCanvasLayer +LOGGER = logging.getLogger('QGIS') + + +#noinspection PyMethodMayBeStatic,PyPep8Naming +class QgisInterface(QObject): + """Class to expose QGIS objects and functions to plugins. + + This class is here for enabling us to run unit tests only, + so most methods are simply stubs. + """ + currentLayerChanged = pyqtSignal(QgsMapCanvasLayer) + + def __init__(self, canvas): + """Constructor + :param canvas: + """ + QObject.__init__(self) + self.canvas = canvas + # Set up slots so we can mimic the behaviour of QGIS when layers + # are added. + LOGGER.debug('Initialising canvas...') + # noinspection PyArgumentList + QgsMapLayerRegistry.instance().layersAdded.connect(self.addLayers) + # noinspection PyArgumentList + QgsMapLayerRegistry.instance().layerWasAdded.connect(self.addLayer) + # noinspection PyArgumentList + QgsMapLayerRegistry.instance().removeAll.connect(self.removeAllLayers) + + # For processing module + self.destCrs = None + + @pyqtSlot('QStringList') + def addLayers(self, layers): + """Handle layers being added to the registry so they show up in canvas. + + :param layers: list list of map layers that were added + + .. note:: The QgsInterface api does not include this method, + it is added here as a helper to facilitate testing. + """ + #LOGGER.debug('addLayers called on qgis_interface') + #LOGGER.debug('Number of layers being added: %s' % len(layers)) + #LOGGER.debug('Layer Count Before: %s' % len(self.canvas.layers())) + current_layers = self.canvas.layers() + final_layers = [] + for layer in current_layers: + final_layers.append(QgsMapCanvasLayer(layer)) + for layer in layers: + final_layers.append(QgsMapCanvasLayer(layer)) + + self.canvas.setLayerSet(final_layers) + #LOGGER.debug('Layer Count After: %s' % len(self.canvas.layers())) + + @pyqtSlot('QgsMapLayer') + def addLayer(self, layer): + """Handle a layer being added to the registry so it shows up in canvas. + + :param layer: list list of map layers that were added + + .. note: The QgsInterface api does not include this method, it is added + here as a helper to facilitate testing. + + .. note: The addLayer method was deprecated in QGIS 1.8 so you should + not need this method much. + """ + pass + + @pyqtSlot() + def removeAllLayers(self): + """Remove layers from the canvas before they get deleted.""" + self.canvas.setLayerSet([]) + + def newProject(self): + """Create new project.""" + # noinspection PyArgumentList + QgsMapLayerRegistry.instance().removeAllMapLayers() + + # ---------------- API Mock for QgsInterface follows ------------------- + + def zoomFull(self): + """Zoom to the map full extent.""" + pass + + def zoomToPrevious(self): + """Zoom to previous view extent.""" + pass + + def zoomToNext(self): + """Zoom to next view extent.""" + pass + + def zoomToActiveLayer(self): + """Zoom to extent of active layer.""" + pass + + def addVectorLayer(self, path, base_name, provider_key): + """Add a vector layer. + + :param path: Path to layer. + :type path: str + + :param base_name: Base name for layer. + :type base_name: str + + :param provider_key: Provider key e.g. 'ogr' + :type provider_key: str + """ + pass + + def addRasterLayer(self, path, base_name): + """Add a raster layer given a raster layer file name + + :param path: Path to layer. + :type path: str + + :param base_name: Base name for layer. + :type base_name: str + """ + pass + + def activeLayer(self): + """Get pointer to the active layer (layer selected in the legend).""" + # noinspection PyArgumentList + layers = QgsMapLayerRegistry.instance().mapLayers() + for item in layers: + return layers[item] + + def addToolBarIcon(self, action): + """Add an icon to the plugins toolbar. + + :param action: Action to add to the toolbar. + :type action: QAction + """ + pass + + def removeToolBarIcon(self, action): + """Remove an action (icon) from the plugin toolbar. + + :param action: Action to add to the toolbar. + :type action: QAction + """ + pass + + def addToolBar(self, name): + """Add toolbar with specified name. + + :param name: Name for the toolbar. + :type name: str + """ + pass + + def mapCanvas(self): + """Return a pointer to the map canvas.""" + return self.canvas + + def mainWindow(self): + """Return a pointer to the main window. + + In case of QGIS it returns an instance of QgisApp. + """ + pass + + def addDockWidget(self, area, dock_widget): + """Add a dock widget to the main window. + + :param area: Where in the ui the dock should be placed. + :type area: + + :param dock_widget: A dock widget to add to the UI. + :type dock_widget: QDockWidget + """ + pass + + def legendInterface(self): + """Get the legend.""" + return self.canvas diff --git a/test/tenbytenraster.asc b/test/tenbytenraster.asc new file mode 100644 index 0000000..96a0ee1 --- /dev/null +++ b/test/tenbytenraster.asc @@ -0,0 +1,19 @@ +NCOLS 10 +NROWS 10 +XLLCENTER 1535380.000000 +YLLCENTER 5083260.000000 +DX 10 +DY 10 +NODATA_VALUE -9999 +0 1 2 3 4 5 6 7 8 9 +0 1 2 3 4 5 6 7 8 9 +0 1 2 3 4 5 6 7 8 9 +0 1 2 3 4 5 6 7 8 9 +0 1 2 3 4 5 6 7 8 9 +0 1 2 3 4 5 6 7 8 9 +0 1 2 3 4 5 6 7 8 9 +0 1 2 3 4 5 6 7 8 9 +0 1 2 3 4 5 6 7 8 9 +0 1 2 3 4 5 6 7 8 9 +CRS +NOTES diff --git a/test/tenbytenraster.asc.aux.xml b/test/tenbytenraster.asc.aux.xml new file mode 100644 index 0000000..cfb1578 --- /dev/null +++ b/test/tenbytenraster.asc.aux.xml @@ -0,0 +1,13 @@ + + + Point + + + + 9 + 4.5 + 0 + 2.872281323269 + + + diff --git a/test/tenbytenraster.keywords b/test/tenbytenraster.keywords new file mode 100644 index 0000000..8be3f61 --- /dev/null +++ b/test/tenbytenraster.keywords @@ -0,0 +1 @@ +title: Tenbytenraster diff --git a/test/tenbytenraster.lic b/test/tenbytenraster.lic new file mode 100644 index 0000000..8345533 --- /dev/null +++ b/test/tenbytenraster.lic @@ -0,0 +1,18 @@ + + + + Tim Sutton, Linfiniti Consulting CC + + + + tenbytenraster.asc + 2700044251 + Yes + Tim Sutton + Tim Sutton (QGIS Source Tree) + Tim Sutton + This data is publicly available from QGIS Source Tree. The original + file was created and contributed to QGIS by Tim Sutton. + + + diff --git a/test/tenbytenraster.prj b/test/tenbytenraster.prj new file mode 100644 index 0000000..a30c00a --- /dev/null +++ b/test/tenbytenraster.prj @@ -0,0 +1 @@ +GEOGCS["GCS_WGS_1984",DATUM["D_WGS_1984",SPHEROID["WGS_1984",6378137,298.257223563]],PRIMEM["Greenwich",0],UNIT["Degree",0.017453292519943295]] \ No newline at end of file diff --git a/test/tenbytenraster.qml b/test/tenbytenraster.qml new file mode 100644 index 0000000..85247d4 --- /dev/null +++ b/test/tenbytenraster.qml @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + 0 + diff --git a/test/test_init.py b/test/test_init.py new file mode 100644 index 0000000..a11ca44 --- /dev/null +++ b/test/test_init.py @@ -0,0 +1,64 @@ +# coding=utf-8 +"""Tests QGIS plugin init.""" + +__author__ = 'Tim Sutton ' +__revision__ = '$Format:%H$' +__date__ = '17/10/2010' +__license__ = "GPL" +__copyright__ = 'Copyright 2012, Australia Indonesia Facility for ' +__copyright__ += 'Disaster Reduction' + +import os +import unittest +import logging +import configparser + +LOGGER = logging.getLogger('QGIS') + + +class TestInit(unittest.TestCase): + """Test that the plugin init is usable for QGIS. + + Based heavily on the validator class by Alessandro + Passoti available here: + + http://github.com/qgis/qgis-django/blob/master/qgis-app/ + plugins/validator.py + + """ + + def test_read_init(self): + """Test that the plugin __init__ will validate on plugins.qgis.org.""" + + # You should update this list according to the latest in + # https://github.com/qgis/qgis-django/blob/master/qgis-app/ + # plugins/validator.py + + required_metadata = [ + 'name', + 'description', + 'version', + 'qgisMinimumVersion', + 'email', + 'author'] + + file_path = os.path.abspath(os.path.join( + os.path.dirname(__file__), os.pardir, + 'metadata.txt')) + LOGGER.info(file_path) + metadata = [] + parser = configparser.ConfigParser() + parser.optionxform = str + parser.read(file_path) + message = 'Cannot find a section named "general" in %s' % file_path + assert parser.has_section('general'), message + metadata.extend(parser.items('general')) + + for expectation in required_metadata: + message = ('Cannot find metadata "%s" in metadata source (%s).' % ( + expectation, file_path)) + + self.assertIn(expectation, dict(metadata), message) + +if __name__ == '__main__': + unittest.main() diff --git a/test/test_qgis_environment.py b/test/test_qgis_environment.py new file mode 100644 index 0000000..1becb30 --- /dev/null +++ b/test/test_qgis_environment.py @@ -0,0 +1,60 @@ +# coding=utf-8 +"""Tests for QGIS functionality. + + +.. note:: This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + +""" +__author__ = 'tim@linfiniti.com' +__date__ = '20/01/2011' +__copyright__ = ('Copyright 2012, Australia Indonesia Facility for ' + 'Disaster Reduction') + +import os +import unittest +from qgis.core import ( + QgsProviderRegistry, + QgsCoordinateReferenceSystem, + QgsRasterLayer) + +from .utilities import get_qgis_app +QGIS_APP = get_qgis_app() + + +class QGISTest(unittest.TestCase): + """Test the QGIS Environment""" + + def test_qgis_environment(self): + """QGIS environment has the expected providers""" + + r = QgsProviderRegistry.instance() + self.assertIn('gdal', r.providerList()) + self.assertIn('ogr', r.providerList()) + self.assertIn('postgres', r.providerList()) + + def test_projection(self): + """Test that QGIS properly parses a wkt string. + """ + crs = QgsCoordinateReferenceSystem() + wkt = ( + 'GEOGCS["GCS_WGS_1984",DATUM["D_WGS_1984",' + 'SPHEROID["WGS_1984",6378137.0,298.257223563]],' + 'PRIMEM["Greenwich",0.0],UNIT["Degree",' + '0.0174532925199433]]') + crs.createFromWkt(wkt) + auth_id = crs.authid() + expected_auth_id = 'EPSG:4326' + self.assertEqual(auth_id, expected_auth_id) + + # now test for a loaded layer + path = os.path.join(os.path.dirname(__file__), 'tenbytenraster.asc') + title = 'TestRaster' + layer = QgsRasterLayer(path, title) + auth_id = layer.crs().authid() + self.assertEqual(auth_id, expected_auth_id) + +if __name__ == '__main__': + unittest.main() diff --git a/test/test_resources.py b/test/test_resources.py new file mode 100644 index 0000000..099318c --- /dev/null +++ b/test/test_resources.py @@ -0,0 +1,44 @@ +# coding=utf-8 +"""Resources test. + +.. note:: This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + +""" + +__author__ = 'info@champs-libres.coop' +__date__ = '2021-03-26' +__copyright__ = 'Copyright 2021, Champs-Libres' + +import unittest + +from qgis.PyQt.QtGui import QIcon + + + +class WebExporterDialogTest(unittest.TestCase): + """Test rerources work.""" + + def setUp(self): + """Runs before each test.""" + pass + + def tearDown(self): + """Runs after each test.""" + pass + + def test_icon_png(self): + """Test we can click OK.""" + path = ':/plugins/WebExporter/icon.png' + icon = QIcon(path) + self.assertFalse(icon.isNull()) + +if __name__ == "__main__": + suite = unittest.makeSuite(WebExporterResourcesTest) + runner = unittest.TextTestRunner(verbosity=2) + runner.run(suite) + + + diff --git a/test/test_translations.py b/test/test_translations.py new file mode 100644 index 0000000..035dc62 --- /dev/null +++ b/test/test_translations.py @@ -0,0 +1,55 @@ +# coding=utf-8 +"""Safe Translations Test. + +.. note:: This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + +""" +from .utilities import get_qgis_app + +__author__ = 'ismailsunni@yahoo.co.id' +__date__ = '12/10/2011' +__copyright__ = ('Copyright 2012, Australia Indonesia Facility for ' + 'Disaster Reduction') +import unittest +import os + +from qgis.PyQt.QtCore import QCoreApplication, QTranslator + +QGIS_APP = get_qgis_app() + + +class SafeTranslationsTest(unittest.TestCase): + """Test translations work.""" + + def setUp(self): + """Runs before each test.""" + if 'LANG' in iter(os.environ.keys()): + os.environ.__delitem__('LANG') + + def tearDown(self): + """Runs after each test.""" + if 'LANG' in iter(os.environ.keys()): + os.environ.__delitem__('LANG') + + def test_qgis_translations(self): + """Test that translations work.""" + parent_path = os.path.join(__file__, os.path.pardir, os.path.pardir) + dir_path = os.path.abspath(parent_path) + file_path = os.path.join( + dir_path, 'i18n', 'af.qm') + translator = QTranslator() + translator.load(file_path) + QCoreApplication.installTranslator(translator) + + expected_message = 'Goeie more' + real_message = QCoreApplication.translate("@default", 'Good morning') + self.assertEqual(real_message, expected_message) + + +if __name__ == "__main__": + suite = unittest.makeSuite(SafeTranslationsTest) + runner = unittest.TextTestRunner(verbosity=2) + runner.run(suite) diff --git a/test/test_web_exporter_dialog.py b/test/test_web_exporter_dialog.py new file mode 100644 index 0000000..20e7c30 --- /dev/null +++ b/test/test_web_exporter_dialog.py @@ -0,0 +1,55 @@ +# coding=utf-8 +"""Dialog test. + +.. note:: This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + +""" + +__author__ = 'info@champs-libres.coop' +__date__ = '2021-03-26' +__copyright__ = 'Copyright 2021, Champs-Libres' + +import unittest + +from qgis.PyQt.QtGui import QDialogButtonBox, QDialog + +from web_exporter_dialog import WebExporterDialog + +from utilities import get_qgis_app +QGIS_APP = get_qgis_app() + + +class WebExporterDialogTest(unittest.TestCase): + """Test dialog works.""" + + def setUp(self): + """Runs before each test.""" + self.dialog = WebExporterDialog(None) + + def tearDown(self): + """Runs after each test.""" + self.dialog = None + + def test_dialog_ok(self): + """Test we can click OK.""" + + button = self.dialog.button_box.button(QDialogButtonBox.Ok) + button.click() + result = self.dialog.result() + self.assertEqual(result, QDialog.Accepted) + + def test_dialog_cancel(self): + """Test we can click cancel.""" + button = self.dialog.button_box.button(QDialogButtonBox.Cancel) + button.click() + result = self.dialog.result() + self.assertEqual(result, QDialog.Rejected) + +if __name__ == "__main__": + suite = unittest.makeSuite(WebExporterDialogTest) + runner = unittest.TextTestRunner(verbosity=2) + runner.run(suite) + diff --git a/test/utilities.py b/test/utilities.py new file mode 100644 index 0000000..be7ee3b --- /dev/null +++ b/test/utilities.py @@ -0,0 +1,61 @@ +# coding=utf-8 +"""Common functionality used by regression tests.""" + +import sys +import logging + + +LOGGER = logging.getLogger('QGIS') +QGIS_APP = None # Static variable used to hold hand to running QGIS app +CANVAS = None +PARENT = None +IFACE = None + + +def get_qgis_app(): + """ Start one QGIS application to test against. + + :returns: Handle to QGIS app, canvas, iface and parent. If there are any + errors the tuple members will be returned as None. + :rtype: (QgsApplication, CANVAS, IFACE, PARENT) + + If QGIS is already running the handle to that app will be returned. + """ + + try: + from qgis.PyQt import QtGui, QtCore + from qgis.core import QgsApplication + from qgis.gui import QgsMapCanvas + from .qgis_interface import QgisInterface + except ImportError: + return None, None, None, None + + global QGIS_APP # pylint: disable=W0603 + + if QGIS_APP is None: + gui_flag = True # All test will run qgis in gui mode + #noinspection PyPep8Naming + QGIS_APP = QgsApplication(sys.argv, gui_flag) + # Make sure QGIS_PREFIX_PATH is set in your env if needed! + QGIS_APP.initQgis() + s = QGIS_APP.showSettings() + LOGGER.debug(s) + + global PARENT # pylint: disable=W0603 + if PARENT is None: + #noinspection PyPep8Naming + PARENT = QtGui.QWidget() + + global CANVAS # pylint: disable=W0603 + if CANVAS is None: + #noinspection PyPep8Naming + CANVAS = QgsMapCanvas(PARENT) + CANVAS.resize(QtCore.QSize(400, 400)) + + global IFACE # pylint: disable=W0603 + if IFACE is None: + # QgisInterface is a stub implementation of the QGIS plugin interface + #noinspection PyPep8Naming + IFACE = QgisInterface(CANVAS) + + return QGIS_APP, CANVAS, IFACE, PARENT diff --git a/web_exporter.py b/web_exporter.py new file mode 100644 index 0000000..6a17d47 --- /dev/null +++ b/web_exporter.py @@ -0,0 +1,200 @@ +# -*- coding: utf-8 -*- +""" +/*************************************************************************** + WebExporter + A QGIS plugin + This plugin exports your vector layers on a remote web server + Generated by Plugin Builder: http://g-sherman.github.io/Qgis-Plugin-Builder/ + ------------------- + begin : 2021-03-26 + git sha : $Format:%H$ + copyright : (C) 2021 by Champs-Libres + email : info@champs-libres.coop + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ +""" +from qgis.PyQt.QtCore import QSettings, QTranslator, QCoreApplication +from qgis.PyQt.QtGui import QIcon +from qgis.PyQt.QtWidgets import QAction + +# Initialize Qt resources from file resources.py +from .resources import * +# Import the code for the dialog +from .web_exporter_dialog import WebExporterDialog +import os.path + + +class WebExporter: + """QGIS Plugin Implementation.""" + + def __init__(self, iface): + """Constructor. + + :param iface: An interface instance that will be passed to this class + which provides the hook by which you can manipulate the QGIS + application at run time. + :type iface: QgsInterface + """ + # Save reference to the QGIS interface + self.iface = iface + # initialize plugin directory + self.plugin_dir = os.path.dirname(__file__) + # initialize locale + locale = QSettings().value('locale/userLocale')[0:2] + locale_path = os.path.join( + self.plugin_dir, + 'i18n', + 'WebExporter_{}.qm'.format(locale)) + + if os.path.exists(locale_path): + self.translator = QTranslator() + self.translator.load(locale_path) + QCoreApplication.installTranslator(self.translator) + + # Declare instance attributes + self.actions = [] + self.menu = self.tr(u'&Web Exporter') + + # Check if plugin was started the first time in current QGIS session + # Must be set in initGui() to survive plugin reloads + self.first_start = None + + # noinspection PyMethodMayBeStatic + def tr(self, message): + """Get the translation for a string using Qt translation API. + + We implement this ourselves since we do not inherit QObject. + + :param message: String for translation. + :type message: str, QString + + :returns: Translated version of message. + :rtype: QString + """ + # noinspection PyTypeChecker,PyArgumentList,PyCallByClass + return QCoreApplication.translate('WebExporter', message) + + + def add_action( + self, + icon_path, + text, + callback, + enabled_flag=True, + add_to_menu=True, + add_to_toolbar=True, + status_tip=None, + whats_this=None, + parent=None): + """Add a toolbar icon to the toolbar. + + :param icon_path: Path to the icon for this action. Can be a resource + path (e.g. ':/plugins/foo/bar.png') or a normal file system path. + :type icon_path: str + + :param text: Text that should be shown in menu items for this action. + :type text: str + + :param callback: Function to be called when the action is triggered. + :type callback: function + + :param enabled_flag: A flag indicating if the action should be enabled + by default. Defaults to True. + :type enabled_flag: bool + + :param add_to_menu: Flag indicating whether the action should also + be added to the menu. Defaults to True. + :type add_to_menu: bool + + :param add_to_toolbar: Flag indicating whether the action should also + be added to the toolbar. Defaults to True. + :type add_to_toolbar: bool + + :param status_tip: Optional text to show in a popup when mouse pointer + hovers over the action. + :type status_tip: str + + :param parent: Parent widget for the new action. Defaults None. + :type parent: QWidget + + :param whats_this: Optional text to show in the status bar when the + mouse pointer hovers over the action. + + :returns: The action that was created. Note that the action is also + added to self.actions list. + :rtype: QAction + """ + + icon = QIcon(icon_path) + action = QAction(icon, text, parent) + action.triggered.connect(callback) + action.setEnabled(enabled_flag) + + if status_tip is not None: + action.setStatusTip(status_tip) + + if whats_this is not None: + action.setWhatsThis(whats_this) + + if add_to_toolbar: + # Adds plugin icon to Plugins toolbar + self.iface.addToolBarIcon(action) + + if add_to_menu: + self.iface.addPluginToVectorMenu( + self.menu, + action) + + self.actions.append(action) + + return action + + def initGui(self): + """Create the menu entries and toolbar icons inside the QGIS GUI.""" + + icon_path = ':/plugins/web_exporter/icon.png' + self.add_action( + icon_path, + text=self.tr(u''), + callback=self.run, + parent=self.iface.mainWindow()) + + # will be set False in run() + self.first_start = True + + + def unload(self): + """Removes the plugin menu item and icon from QGIS GUI.""" + for action in self.actions: + self.iface.removePluginVectorMenu( + self.tr(u'&Web Exporter'), + action) + self.iface.removeToolBarIcon(action) + + + def run(self): + """Run method that performs all the real work""" + + # Create the dialog with elements (after translation) and keep reference + # Only create GUI ONCE in callback, so that it will only load when the plugin is started + if self.first_start == True: + self.first_start = False + self.dlg = WebExporterDialog() + + # show the dialog + self.dlg.show() + # Run the dialog event loop + result = self.dlg.exec_() + # See if OK was pressed + if result: + # Do something useful here - delete the line containing pass and + # substitute with your code. + pass diff --git a/web_exporter_dialog.py b/web_exporter_dialog.py new file mode 100644 index 0000000..bedda67 --- /dev/null +++ b/web_exporter_dialog.py @@ -0,0 +1,44 @@ +# -*- coding: utf-8 -*- +""" +/*************************************************************************** + WebExporterDialog + A QGIS plugin + This plugin exports your vector layers on a remote web server + Generated by Plugin Builder: http://g-sherman.github.io/Qgis-Plugin-Builder/ + ------------------- + begin : 2021-03-26 + git sha : $Format:%H$ + copyright : (C) 2021 by Champs-Libres + email : info@champs-libres.coop + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ +""" + +import os + +from qgis.PyQt import uic +from qgis.PyQt import QtWidgets + +# This loads your .ui file so that PyQt can populate your plugin with the elements from Qt Designer +FORM_CLASS, _ = uic.loadUiType(os.path.join( + os.path.dirname(__file__), 'web_exporter_dialog_base.ui')) + + +class WebExporterDialog(QtWidgets.QDialog, FORM_CLASS): + def __init__(self, parent=None): + """Constructor.""" + super(WebExporterDialog, self).__init__(parent) + # Set up the user interface from Designer through FORM_CLASS. + # After self.setupUi() you can access any designer object by doing + # self., and you can use autoconnect slots - see + # http://qt-project.org/doc/qt-4.8/designer-using-a-ui-file.html + # #widgets-and-dialogs-with-auto-connect + self.setupUi(self) diff --git a/web_exporter_dialog_base.ui b/web_exporter_dialog_base.ui new file mode 100644 index 0000000..b5f5909 --- /dev/null +++ b/web_exporter_dialog_base.ui @@ -0,0 +1,67 @@ + + WebExporterDialogBase + + + + 0 + 0 + 400 + 300 + + + + Web Exporter + + + + + 30 + 240 + 341 + 32 + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + button_box + accepted() + WebExporterDialogBase + accept() + + + 248 + 254 + + + 157 + 274 + + + + + button_box + rejected() + WebExporterDialogBase + reject() + + + 316 + 260 + + + 286 + 274 + + + + +