Initial commit
This commit is contained in:
		
						commit
						73780d9530
					
				
							
								
								
									
										2
									
								
								.flake8
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								.flake8
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,2 @@ | ||||
| [flake8] | ||||
| format = ${cyan}%(path)s${reset}:${yellow_bold}%(row)d${reset}:${green_bold}%(col)d${reset}: ${red_bold}%(code)s${reset} %(text)s | ||||
							
								
								
									
										8
									
								
								.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,8 @@ | ||||
| .vscode/ | ||||
| htmlcov/ | ||||
| instance/ | ||||
| node_modules/ | ||||
| uploads/ | ||||
| venv/ | ||||
| *.pyc | ||||
| *.db | ||||
							
								
								
									
										564
									
								
								.pylintrc
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										564
									
								
								.pylintrc
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,564 @@ | ||||
| [MASTER] | ||||
| 
 | ||||
| # A comma-separated list of package or module names from where C extensions may | ||||
| # be loaded. Extensions are loading into the active Python interpreter and may | ||||
| # run arbitrary code. | ||||
| extension-pkg-whitelist= | ||||
| 
 | ||||
| # Add files or directories to the blacklist. They should be base names, not | ||||
| # paths. | ||||
| ignore=CVS,migrations | ||||
| 
 | ||||
| # Add files or directories matching the regex patterns to the blacklist. The | ||||
| # regex matches against base names, not paths. | ||||
| ignore-patterns= | ||||
| 
 | ||||
| # Python code to execute, usually for sys.path manipulation such as | ||||
| # pygtk.require(). | ||||
| #init-hook= | ||||
| 
 | ||||
| # Use multiple processes to speed up Pylint. Specifying 0 will auto-detect the | ||||
| # number of processors available to use. | ||||
| jobs=1 | ||||
| 
 | ||||
| # Control the amount of potential inferred values when inferring a single | ||||
| # object. This can help the performance when dealing with large functions or | ||||
| # complex, nested conditions. | ||||
| limit-inference-results=100 | ||||
| 
 | ||||
| # List of plugins (as comma separated values of python modules names) to load, | ||||
| # usually to register additional checkers. | ||||
| load-plugins= | ||||
| 
 | ||||
| # Pickle collected data for later comparisons. | ||||
| persistent=yes | ||||
| 
 | ||||
| # Specify a configuration file. | ||||
| #rcfile= | ||||
| 
 | ||||
| # When enabled, pylint would attempt to guess common misconfiguration and emit | ||||
| # user-friendly hints instead of false-positive error messages. | ||||
| suggestion-mode=yes | ||||
| 
 | ||||
| # Allow loading of arbitrary C extensions. Extensions are imported into the | ||||
| # active Python interpreter and may run arbitrary code. | ||||
| unsafe-load-any-extension=no | ||||
| 
 | ||||
| 
 | ||||
| [MESSAGES CONTROL] | ||||
| 
 | ||||
| # Only show warnings with the listed confidence levels. Leave empty to show | ||||
| # all. Valid levels: HIGH, INFERENCE, INFERENCE_FAILURE, UNDEFINED. | ||||
| confidence= | ||||
| 
 | ||||
| # 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". | ||||
| disable=print-statement, | ||||
|         parameter-unpacking, | ||||
|         unpacking-in-except, | ||||
|         old-raise-syntax, | ||||
|         backtick, | ||||
|         long-suffix, | ||||
|         old-ne-operator, | ||||
|         old-octal-literal, | ||||
|         import-star-module-level, | ||||
|         non-ascii-bytes-literal, | ||||
|         raw-checker-failed, | ||||
|         bad-inline-option, | ||||
|         locally-disabled, | ||||
|         file-ignored, | ||||
|         suppressed-message, | ||||
|         useless-suppression, | ||||
|         deprecated-pragma, | ||||
|         use-symbolic-message-instead, | ||||
|         apply-builtin, | ||||
|         basestring-builtin, | ||||
|         buffer-builtin, | ||||
|         cmp-builtin, | ||||
|         coerce-builtin, | ||||
|         execfile-builtin, | ||||
|         file-builtin, | ||||
|         long-builtin, | ||||
|         raw_input-builtin, | ||||
|         reduce-builtin, | ||||
|         standarderror-builtin, | ||||
|         unicode-builtin, | ||||
|         xrange-builtin, | ||||
|         coerce-method, | ||||
|         delslice-method, | ||||
|         getslice-method, | ||||
|         setslice-method, | ||||
|         no-absolute-import, | ||||
|         old-division, | ||||
|         dict-iter-method, | ||||
|         dict-view-method, | ||||
|         next-method-called, | ||||
|         metaclass-assignment, | ||||
|         indexing-exception, | ||||
|         raising-string, | ||||
|         reload-builtin, | ||||
|         oct-method, | ||||
|         hex-method, | ||||
|         nonzero-method, | ||||
|         cmp-method, | ||||
|         input-builtin, | ||||
|         round-builtin, | ||||
|         intern-builtin, | ||||
|         unichr-builtin, | ||||
|         map-builtin-not-iterating, | ||||
|         zip-builtin-not-iterating, | ||||
|         range-builtin-not-iterating, | ||||
|         filter-builtin-not-iterating, | ||||
|         using-cmp-argument, | ||||
|         eq-without-hash, | ||||
|         div-method, | ||||
|         idiv-method, | ||||
|         rdiv-method, | ||||
|         exception-message-attribute, | ||||
|         invalid-str-codec, | ||||
|         sys-max-int, | ||||
|         bad-python3-import, | ||||
|         deprecated-string-function, | ||||
|         deprecated-str-translate-call, | ||||
|         deprecated-itertools-function, | ||||
|         deprecated-types-field, | ||||
|         next-method-defined, | ||||
|         dict-items-not-iterating, | ||||
|         dict-keys-not-iterating, | ||||
|         dict-values-not-iterating, | ||||
|         deprecated-operator-function, | ||||
|         deprecated-urllib-function, | ||||
|         xreadlines-attribute, | ||||
|         deprecated-sys-function, | ||||
|         exception-escape, | ||||
|         comprehension-escape, | ||||
|         too-few-public-methods,  # custom | ||||
|         too-many-instance-attributes,  # custom | ||||
| 
 | ||||
| # 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 (only on the command line, not in the configuration file where | ||||
| # it should appear only once). See also the "--disable" option for examples. | ||||
| enable=c-extension-no-member | ||||
| 
 | ||||
| 
 | ||||
| [REPORTS] | ||||
| 
 | ||||
| # 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) | ||||
| 
 | ||||
| # 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= | ||||
| 
 | ||||
| # Set the output format. Available formats are text, parseable, colorized, json | ||||
| # and msvs (visual studio). You can also give a reporter class, e.g. | ||||
| # mypackage.mymodule.MyReporterClass. | ||||
| output-format=text | ||||
| 
 | ||||
| # Tells whether to display a full report or only the messages. | ||||
| reports=no | ||||
| 
 | ||||
| # Activate the evaluation score. | ||||
| score=yes | ||||
| 
 | ||||
| 
 | ||||
| [REFACTORING] | ||||
| 
 | ||||
| # Maximum number of nested blocks for function / method body | ||||
| max-nested-blocks=5 | ||||
| 
 | ||||
| # Complete name of functions that never returns. When checking for | ||||
| # inconsistent-return-statements if a never returning function is called then | ||||
| # it will be considered as an explicit return statement and no message will be | ||||
| # printed. | ||||
| never-returning-functions=sys.exit | ||||
| 
 | ||||
| 
 | ||||
| [BASIC] | ||||
| 
 | ||||
| # Naming style matching correct argument names. | ||||
| argument-naming-style=snake_case | ||||
| 
 | ||||
| # Regular expression matching correct argument names. Overrides argument- | ||||
| # naming-style. | ||||
| #argument-rgx= | ||||
| 
 | ||||
| # Naming style matching correct attribute names. | ||||
| attr-naming-style=snake_case | ||||
| 
 | ||||
| # Regular expression matching correct attribute names. Overrides attr-naming- | ||||
| # style. | ||||
| #attr-rgx= | ||||
| 
 | ||||
| # Bad variable names which should always be refused, separated by a comma. | ||||
| bad-names=foo, | ||||
|           bar, | ||||
|           baz, | ||||
|           toto, | ||||
|           tutu, | ||||
|           tata | ||||
| 
 | ||||
| # Naming style matching correct class attribute names. | ||||
| class-attribute-naming-style=any | ||||
| 
 | ||||
| # Regular expression matching correct class attribute names. Overrides class- | ||||
| # attribute-naming-style. | ||||
| #class-attribute-rgx= | ||||
| 
 | ||||
| # Naming style matching correct class names. | ||||
| class-naming-style=PascalCase | ||||
| 
 | ||||
| # Regular expression matching correct class names. Overrides class-naming- | ||||
| # style. | ||||
| #class-rgx= | ||||
| 
 | ||||
| # Naming style matching correct constant names. | ||||
| const-naming-style=any | ||||
| 
 | ||||
| # Regular expression matching correct constant names. Overrides const-naming- | ||||
| # style. | ||||
| #const-rgx= | ||||
| 
 | ||||
| # Minimum line length for functions/classes that require docstrings, shorter | ||||
| # ones are exempt. | ||||
| docstring-min-length=-1 | ||||
| 
 | ||||
| # Naming style matching correct function names. | ||||
| function-naming-style=snake_case | ||||
| 
 | ||||
| # Regular expression matching correct function names. Overrides function- | ||||
| # naming-style. | ||||
| #function-rgx= | ||||
| 
 | ||||
| # Good variable names which should always be accepted, separated by a comma. | ||||
| good-names=i, | ||||
|            j, | ||||
|            k, | ||||
|            ex, | ||||
|            Run, | ||||
|            _ | ||||
| 
 | ||||
| # Include a hint for the correct naming format with invalid-name. | ||||
| include-naming-hint=no | ||||
| 
 | ||||
| # Naming style matching correct inline iteration names. | ||||
| inlinevar-naming-style=any | ||||
| 
 | ||||
| # Regular expression matching correct inline iteration names. Overrides | ||||
| # inlinevar-naming-style. | ||||
| #inlinevar-rgx= | ||||
| 
 | ||||
| # Naming style matching correct method names. | ||||
| method-naming-style=snake_case | ||||
| 
 | ||||
| # Regular expression matching correct method names. Overrides method-naming- | ||||
| # style. | ||||
| #method-rgx= | ||||
| 
 | ||||
| # Naming style matching correct module names. | ||||
| module-naming-style=snake_case | ||||
| 
 | ||||
| # Regular expression matching correct module names. Overrides module-naming- | ||||
| # style. | ||||
| #module-rgx= | ||||
| 
 | ||||
| # Colon-delimited sets of names that determine each other's naming style when | ||||
| # the name regexes allow several styles. | ||||
| name-group= | ||||
| 
 | ||||
| # Regular expression which should only match function or class names that do | ||||
| # not require a docstring. | ||||
| no-docstring-rgx=^_ | ||||
| 
 | ||||
| # List of decorators that produce properties, such as abc.abstractproperty. Add | ||||
| # to this list to register other decorators that produce valid properties. | ||||
| # These decorators are taken in consideration only for invalid-name. | ||||
| property-classes=abc.abstractproperty | ||||
| 
 | ||||
| # Naming style matching correct variable names. | ||||
| variable-naming-style=snake_case | ||||
| 
 | ||||
| # Regular expression matching correct variable names. Overrides variable- | ||||
| # naming-style. | ||||
| #variable-rgx= | ||||
| 
 | ||||
| 
 | ||||
| [MISCELLANEOUS] | ||||
| 
 | ||||
| # List of note tags to take in consideration, separated by a comma. | ||||
| notes=FIXME, | ||||
|       XXX, | ||||
|       TODO | ||||
| 
 | ||||
| 
 | ||||
| [FORMAT] | ||||
| 
 | ||||
| # Expected format of line ending, e.g. empty (any line ending), LF or CRLF. | ||||
| expected-line-ending-format= | ||||
| 
 | ||||
| # Regexp for a line that is allowed to be longer than the limit. | ||||
| ignore-long-lines=^\s*(# )?<?https?://\S+>?$ | ||||
| 
 | ||||
| # Number of spaces of indent required inside a hanging or continued line. | ||||
| indent-after-paren=4 | ||||
| 
 | ||||
| # String used as indentation unit. This is usually "    " (4 spaces) or "\t" (1 | ||||
| # tab). | ||||
| indent-string='    ' | ||||
| 
 | ||||
| # Maximum number of characters on a single line. | ||||
| max-line-length=100 | ||||
| 
 | ||||
| # Maximum number of lines in a module. | ||||
| max-module-lines=1000 | ||||
| 
 | ||||
| # List of optional constructs for which whitespace checking is disabled. `dict- | ||||
| # separator` is used to allow tabulation in dicts, etc.: {1  : 1,\n222: 2}. | ||||
| # `trailing-comma` allows a space between comma and closing bracket: (a, ). | ||||
| # `empty-line` allows space-only lines. | ||||
| no-space-check=trailing-comma, | ||||
|                dict-separator | ||||
| 
 | ||||
| # Allow the body of a class to be on the same line as the declaration if body | ||||
| # contains single statement. | ||||
| single-line-class-stmt=no | ||||
| 
 | ||||
| # 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 | ||||
| 
 | ||||
| 
 | ||||
| [VARIABLES] | ||||
| 
 | ||||
| # List of additional names supposed to be defined in builtins. Remember that | ||||
| # you should avoid defining new builtins when possible. | ||||
| additional-builtins= | ||||
| 
 | ||||
| # Tells whether unused global variables should be treated as a violation. | ||||
| allow-global-unused-variables=yes | ||||
| 
 | ||||
| # List of strings which can identify a callback function by name. A callback | ||||
| # name must start or end with one of those strings. | ||||
| callbacks=cb_, | ||||
|           _cb | ||||
| 
 | ||||
| # A regular expression matching the name of dummy variables (i.e. expected to | ||||
| # not be used). | ||||
| dummy-variables-rgx=_+$|(_[a-zA-Z0-9_]*[a-zA-Z0-9]+?$)|dummy|^ignored_|^unused_ | ||||
| 
 | ||||
| # Argument names that match this expression will be ignored. Default to name | ||||
| # with leading underscore. | ||||
| ignored-argument-names=_.*|^ignored_|^unused_ | ||||
| 
 | ||||
| # Tells whether we should check for unused import in __init__ files. | ||||
| init-import=no | ||||
| 
 | ||||
| # List of qualified module names which can have objects that can redefine | ||||
| # builtins. | ||||
| redefining-builtins-modules=six.moves,past.builtins,future.builtins,builtins,io | ||||
| 
 | ||||
| 
 | ||||
| [LOGGING] | ||||
| 
 | ||||
| # Format style used to check logging format string. `old` means using % | ||||
| # formatting, while `new` is for `{}` formatting. | ||||
| logging-format-style=old | ||||
| 
 | ||||
| # Logging modules to check that the string format arguments are in logging | ||||
| # function parameter format. | ||||
| logging-modules=logging | ||||
| 
 | ||||
| 
 | ||||
| [TYPECHECK] | ||||
| 
 | ||||
| # List of decorators that produce context managers, such as | ||||
| # contextlib.contextmanager. Add to this list to register other decorators that | ||||
| # produce valid context managers. | ||||
| contextmanager-decorators=contextlib.contextmanager | ||||
| 
 | ||||
| # List of members which are set dynamically and missed by pylint inference | ||||
| # system, and so shouldn't trigger E1101 when accessed. Python regular | ||||
| # expressions are accepted. | ||||
| generated-members= | ||||
| 
 | ||||
| # 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 | ||||
| 
 | ||||
| # Tells whether to warn about missing members when the owner of the attribute | ||||
| # is inferred to be None. | ||||
| ignore-none=yes | ||||
| 
 | ||||
| # This flag controls whether pylint should warn about no-member and similar | ||||
| # checks whenever an opaque object is returned when inferring. The inference | ||||
| # can return multiple potential results while evaluating a Python object, but | ||||
| # some branches might not be evaluated, which results in partial inference. In | ||||
| # that case, it might be useful to still emit no-member and other checks for | ||||
| # the rest of the inferred objects. | ||||
| ignore-on-opaque-inference=yes | ||||
| 
 | ||||
| # List of class names for which member attributes should not be checked (useful | ||||
| # for classes with dynamically set attributes). This supports the use of | ||||
| # qualified names. | ||||
| ignored-classes=optparse.Values,thread._local,_thread._local, | ||||
|       scoped_session | ||||
| 
 | ||||
| # List of module names for which member attributes should not be checked | ||||
| # (useful for modules/projects where namespaces are manipulated during runtime | ||||
| # and thus existing member attributes cannot be deduced by static analysis. It | ||||
| # supports qualified module names, as well as Unix pattern matching. | ||||
| ignored-modules= | ||||
| 
 | ||||
| # Show a hint with possible names when a member name was not found. The aspect | ||||
| # of finding the hint is based on edit distance. | ||||
| missing-member-hint=yes | ||||
| 
 | ||||
| # The minimum edit distance a name should have in order to be considered a | ||||
| # similar match for a missing member name. | ||||
| missing-member-hint-distance=1 | ||||
| 
 | ||||
| # The total number of similar names that should be taken in consideration when | ||||
| # showing a hint for a missing member. | ||||
| missing-member-max-choices=1 | ||||
| 
 | ||||
| 
 | ||||
| [SIMILARITIES] | ||||
| 
 | ||||
| # Ignore comments when computing similarities. | ||||
| ignore-comments=yes | ||||
| 
 | ||||
| # Ignore docstrings when computing similarities. | ||||
| ignore-docstrings=yes | ||||
| 
 | ||||
| # Ignore imports when computing similarities. | ||||
| ignore-imports=no | ||||
| 
 | ||||
| # Minimum lines number of a similarity. | ||||
| # default 4: problematic in tests. consider switching back later | ||||
| min-similarity-lines=4 | ||||
| 
 | ||||
| [SPELLING] | ||||
| 
 | ||||
| # Limits count of emitted suggestions for spelling mistakes. | ||||
| max-spelling-suggestions=4 | ||||
| 
 | ||||
| # Spelling dictionary name. Available dictionaries: none. To make it working | ||||
| # install python-enchant package.. | ||||
| spelling-dict= | ||||
| 
 | ||||
| # List of comma separated words that should not be checked. | ||||
| spelling-ignore-words= | ||||
| 
 | ||||
| # A path to a file that contains private dictionary; one word per line. | ||||
| spelling-private-dict-file= | ||||
| 
 | ||||
| # Tells whether to store unknown words to indicated private dictionary in | ||||
| # --spelling-private-dict-file option instead of raising a message. | ||||
| spelling-store-unknown-words=no | ||||
| 
 | ||||
| 
 | ||||
| [DESIGN] | ||||
| 
 | ||||
| # Maximum number of arguments for function / method. | ||||
| max-args=5 | ||||
| 
 | ||||
| # Maximum number of attributes for a class (see R0902). | ||||
| max-attributes=7 | ||||
| 
 | ||||
| # Maximum number of boolean expressions in an if statement. | ||||
| max-bool-expr=5 | ||||
| 
 | ||||
| # Maximum number of branch for function / method body. | ||||
| max-branches=12 | ||||
| 
 | ||||
| # Maximum number of locals for function / method body. | ||||
| max-locals=15 | ||||
| 
 | ||||
| # Maximum number of parents for a class (see R0901). | ||||
| max-parents=7 | ||||
| 
 | ||||
| # Maximum number of public methods for a class (see R0904). | ||||
| max-public-methods=20 | ||||
| 
 | ||||
| # Maximum number of return / yield for function / method body. | ||||
| max-returns=6 | ||||
| 
 | ||||
| # Maximum number of statements in function / method body. | ||||
| max-statements=50 | ||||
| 
 | ||||
| # Minimum number of public methods for a class (see R0903). | ||||
| min-public-methods=2 | ||||
| 
 | ||||
| 
 | ||||
| [CLASSES] | ||||
| 
 | ||||
| # List of method names used to declare (i.e. assign) instance attributes. | ||||
| defining-attr-methods=__init__, | ||||
|                       __new__, | ||||
|                       setUp | ||||
| 
 | ||||
| # List of member names, which should be excluded from the protected access | ||||
| # warning. | ||||
| exclude-protected=_asdict, | ||||
|                   _fields, | ||||
|                   _replace, | ||||
|                   _source, | ||||
|                   _make | ||||
| 
 | ||||
| # 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=cls | ||||
| 
 | ||||
| 
 | ||||
| [IMPORTS] | ||||
| 
 | ||||
| # Allow wildcard imports from modules that define __all__. | ||||
| allow-wildcard-with-all=no | ||||
| 
 | ||||
| # Analyse import fallback blocks. This can be used to support both Python 2 and | ||||
| # 3 compatible code, which means that the block might have code that exists | ||||
| # only in one or another interpreter, leading to false positives when analysed. | ||||
| analyse-fallback-blocks=no | ||||
| 
 | ||||
| # Deprecated modules which should not be used, separated by a comma. | ||||
| deprecated-modules=optparse,tkinter.tix | ||||
| 
 | ||||
| # Create a graph of external dependencies in the given file (report RP0402 must | ||||
| # not be disabled). | ||||
| ext-import-graph= | ||||
| 
 | ||||
| # 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 internal dependencies in the given file (report RP0402 must | ||||
| # not be disabled). | ||||
| int-import-graph= | ||||
| 
 | ||||
| # Force import order to recognize a module as part of the standard | ||||
| # compatibility libraries. | ||||
| known-standard-library= | ||||
| 
 | ||||
| # Force import order to recognize a module as part of a third party library. | ||||
| known-third-party=enchant | ||||
| 
 | ||||
| 
 | ||||
| [EXCEPTIONS] | ||||
| 
 | ||||
| # Exceptions that will emit a warning when being caught. Defaults to | ||||
| # "Exception". | ||||
| overgeneral-exceptions=Exception | ||||
							
								
								
									
										79
									
								
								Makefile
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										79
									
								
								Makefile
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,79 @@ | ||||
| # setup
 | ||||
| 
 | ||||
| init: | ||||
| 	mkdir -p instance | ||||
| 	cp app/config.py instance/config.py | ||||
| 
 | ||||
| venv: | ||||
| 	python3 -m venv venv | ||||
| 	venv/bin/pip install --upgrade pip | ||||
| 
 | ||||
| install: | ||||
| 	venv/bin/pip install -r requirements.txt | ||||
| 
 | ||||
| list_outdated: | ||||
| 	venv/bin/pip list --outdated | ||||
| 
 | ||||
| # shortcuts
 | ||||
| 
 | ||||
| bootstrap: | ||||
| 	make init | ||||
| 	make venv | ||||
| 	make install | ||||
| 
 | ||||
| clean: | ||||
| 	rm -rf venv | ||||
| 	rm -rf instance | ||||
| 
 | ||||
| # run server
 | ||||
| 
 | ||||
| run: | ||||
| 	FLASK_ENV=development venv/bin/flask run | ||||
| 
 | ||||
| prod: | ||||
| 	venv/bin/gunicorn -w 4 -b 127.0.0.1:4000 "manage:app" | ||||
| 
 | ||||
| # database
 | ||||
| 
 | ||||
| refresh-db: | ||||
| 	venv/bin/flask database recreate-db | ||||
| 
 | ||||
| seed-db: | ||||
| 	venv/bin/flask database seed-db | ||||
| 
 | ||||
| # linting
 | ||||
| 
 | ||||
| pylint: | ||||
| 	venv/bin/pylint app --output-format=colorized | ||||
| 	venv/bin/pylint tests --output-format=colorized -d duplicate-code | ||||
| 
 | ||||
| flake8: | ||||
| 	venv/bin/flake8 --exclude=migrations app tests | ||||
| 
 | ||||
| lint: | ||||
| 	# ignores errors: for development only | ||||
| 	# ===== FLAKE8 ===== | ||||
| 	-make flake8 | ||||
| 	# ===== PYLINT =====   | ||||
| 	-make pylint | ||||
| 
 | ||||
| lint-watch: | ||||
| 	-make lint | ||||
| 	@echo "\n" | ||||
| 	while inotifywait -re close_write app tests; do make lint; echo "\n"; done | ||||
| 
 | ||||
| # testing
 | ||||
| 
 | ||||
| test: | ||||
| 	FLASK_ENV=testing venv/bin/flask test | ||||
| 
 | ||||
| cov: | ||||
| 	FLASK_ENV=testing venv/bin/flask test-coverage | ||||
| 
 | ||||
| test-watch: | ||||
| 	-make test | ||||
| 	while inotifywait -re close_write app tests; do make test; done | ||||
| 
 | ||||
| cov-watch: | ||||
| 	-make cov | ||||
| 	while inotifywait -re close_write app tests; do make cov; done | ||||
							
								
								
									
										56
									
								
								app/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										56
									
								
								app/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,56 @@ | ||||
| """The main app entrypoint""" | ||||
| 
 | ||||
| import logging | ||||
| from flask import Flask, render_template | ||||
| from flask_alembic import Alembic | ||||
| from flask_bcrypt import Bcrypt | ||||
| from flask_mail import Mail | ||||
| from flask_sqlalchemy import SQLAlchemy | ||||
| from flask_debugtoolbar import DebugToolbarExtension | ||||
| 
 | ||||
| alembic = Alembic() | ||||
| bcrypt = Bcrypt() | ||||
| mail = Mail() | ||||
| db = SQLAlchemy() | ||||
| toolbar = DebugToolbarExtension() | ||||
| 
 | ||||
| 
 | ||||
| def create_app(**config): | ||||
|     """create a flask instance and initialize plugins""" | ||||
|     app = Flask(__name__) | ||||
| 
 | ||||
|     app.config.from_pyfile('config.py') | ||||
|     app.config.from_pyfile('../instance/config.py', silent=True) | ||||
|     app.config.from_mapping(config) | ||||
|     if app.config['ENV'] == 'testing':  # pragma: no branch | ||||
|         app.config['TESTING'] = True  # Flask-Mail needs this | ||||
|         app.config.update(SQLALCHEMY_DATABASE_URI=app.config['TESTING_DB']) | ||||
| 
 | ||||
|     alembic.init_app(app) | ||||
|     bcrypt.init_app(app) | ||||
|     mail.init_app(app) | ||||
|     db.init_app(app) | ||||
|     toolbar.init_app(app) | ||||
| 
 | ||||
|     # register index page | ||||
|     app.add_url_rule('/', 'index', lambda: render_template('index.html')) | ||||
| 
 | ||||
|     # register blueprints | ||||
|     # pylint: disable=cyclic-import | ||||
|     from .blueprints import BLUEPRINTS | ||||
|     for blueprint in BLUEPRINTS: | ||||
|         app.register_blueprint(blueprint) | ||||
| 
 | ||||
|     # register lifecycle methods | ||||
|     from app.lifecycle import authenticate | ||||
|     app.before_request(authenticate) | ||||
| 
 | ||||
|     # setup logging of 500 errors | ||||
|     # pylint: disable=unused-argument | ||||
|     def internal_error_handler(exc):  # pragma: no cover | ||||
|         logger = logging.getLogger('app.errors') | ||||
|         logger.exception('Internal Server Error') | ||||
|         return '500 error', 500 | ||||
|     app.register_error_handler(500, internal_error_handler) | ||||
| 
 | ||||
|     return app | ||||
							
								
								
									
										7
									
								
								app/blueprints.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								app/blueprints.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,7 @@ | ||||
| """gather blueprints from modules for easier registering""" | ||||
| 
 | ||||
| from app.routes.auth import auth_bp | ||||
| from app.routes.tiles import tiles_bp | ||||
| from app.routes.map import map_bp | ||||
| 
 | ||||
| BLUEPRINTS = [auth_bp, tiles_bp, map_bp] | ||||
							
								
								
									
										38
									
								
								app/config.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								app/config.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,38 @@ | ||||
| """Configuration file""" | ||||
| 
 | ||||
| # ===== Flask Settings ===== | ||||
| SECRET_KEY = "secret-key-to-be-overwritten" | ||||
| UPLOAD_FOLDER = 'uploads' | ||||
| ALLOWED_EXTENSIONS = ['png'] | ||||
| 
 | ||||
| # ===== Debug-Toolbar ===== | ||||
| DEBUG_TB_INTERCEPT_REDIRECTS = False | ||||
| 
 | ||||
| # ===== Flask-Bcrypt | ||||
| BCRYPT_LOG_ROUNDS = 12 | ||||
| BCRYPT_HASH_PREFIX = '2b' | ||||
| BCRYPT_HANDLE_LONG_PASSWORDS = True | ||||
| 
 | ||||
| # ===== Database ===== | ||||
| SQLALCHEMY_DATABASE_URI = 'sqlite:///app.db' | ||||
| TESTING_DB = 'sqlite:///testing.db' | ||||
| SQLALCHEMY_ECHO = False | ||||
| SQLALCHEMY_TRACK_MODIFICATIONS = False | ||||
| 
 | ||||
| # ===== WTF ===== | ||||
| WTF_CSRF_ENABLED = True | ||||
| WTF_CSRF_TIME_LIMIT = 3600  # 1 hour | ||||
| 
 | ||||
| # ===== Flask-Mail ===== | ||||
| MAIL_SERVER = 'localhost' | ||||
| MAIL_PORT = 25 | ||||
| MAIL_USE_TLS = False | ||||
| MAIL_USE_SSL = False | ||||
| # MAIL_DEBUG = app.debug | ||||
| MAIL_USERNAME = None | ||||
| MAIL_PASSWORD = None | ||||
| DEFAULT_MAIL_SENDER = 'qerida' | ||||
| MAIL_MAX_EMAILS = None | ||||
| MAIL_SUPPRESS_SEND = True | ||||
| MAIL_ASCII_ATTACHMENTS = False | ||||
| MAIL_ASYNC_SEND = True | ||||
							
								
								
									
										17
									
								
								app/forms.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								app/forms.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,17 @@ | ||||
| """Forms for the application""" | ||||
| 
 | ||||
| from flask_wtf import FlaskForm | ||||
| from wtforms import StringField, PasswordField | ||||
| from wtforms.validators import DataRequired, Length | ||||
| 
 | ||||
| 
 | ||||
| class RegisterForm(FlaskForm): | ||||
|     """Register one user""" | ||||
|     name = StringField('Nickname', validators=[DataRequired(), Length(3, 25)]) | ||||
|     password = PasswordField('Password', validators=[Length(6)]) | ||||
| 
 | ||||
| 
 | ||||
| class LoginForm(FlaskForm): | ||||
|     """Login one user""" | ||||
|     name = StringField('Name') | ||||
|     password = PasswordField('Password') | ||||
							
								
								
									
										16
									
								
								app/lifecycle.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								app/lifecycle.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,16 @@ | ||||
| """Methods for app lifecycle (before/after request, ...)""" | ||||
| 
 | ||||
| from flask import g, session | ||||
| 
 | ||||
| from app.models import User | ||||
| 
 | ||||
| 
 | ||||
| def authenticate(): | ||||
|     """read session to authenticate user. Stores user object in flask.g | ||||
|      | ||||
|     For use in before_request | ||||
|     """ | ||||
|     g.user = None | ||||
|     if session.get('auth') is not None: | ||||
|         user = User.query.get(session['auth']) | ||||
|         g.user = user | ||||
							
								
								
									
										44
									
								
								app/models.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										44
									
								
								app/models.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,44 @@ | ||||
| """Database models""" | ||||
| 
 | ||||
| from sqlalchemy import Column, Integer, String, DateTime, ForeignKey | ||||
| from sqlalchemy.sql import func | ||||
| 
 | ||||
| from app import db, bcrypt | ||||
| 
 | ||||
| 
 | ||||
| class User(db.Model): | ||||
|     """A registered user""" | ||||
|     id = Column(Integer, primary_key=True) | ||||
|     name = Column(String(255), nullable=False) | ||||
|     _password = Column('password', String(60)) | ||||
|     created_at = Column(DateTime, default=func.now()) | ||||
| 
 | ||||
|     @property | ||||
|     def password(self): | ||||
|         """Getter for the password column""" | ||||
|         return self._password | ||||
| 
 | ||||
|     @password.setter | ||||
|     def password(self, value): | ||||
|         if not value: | ||||
|             return | ||||
|         value = value.encode('utf-8') | ||||
|         self._password = bcrypt.generate_password_hash(value).decode('utf-8') | ||||
| 
 | ||||
|     def authenticate(self, plaintext): | ||||
|         """checks the plaintext password against the stored hash""" | ||||
|         if self.password is None: | ||||
|             return False | ||||
|         stored_hash = self.password.encode('utf-8') | ||||
|         plaintext = plaintext.encode('utf-8') | ||||
|         return bcrypt.check_password_hash(stored_hash, plaintext) | ||||
| 
 | ||||
| 
 | ||||
| class Tile(db.Model): | ||||
|     """A map tile""" | ||||
|     id = Column(Integer, primary_key=True) | ||||
|     x = Column(Integer, nullable=False) | ||||
|     y = Column(Integer, nullable=False) | ||||
|     z = Column(Integer, nullable=False) | ||||
|     created_at = Column(DateTime, default=func.now()) | ||||
|     user_id = Column(Integer, ForeignKey('user.id')) | ||||
							
								
								
									
										0
									
								
								app/routes/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								app/routes/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										48
									
								
								app/routes/auth.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										48
									
								
								app/routes/auth.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,48 @@ | ||||
| """Authentication routes""" | ||||
| 
 | ||||
| from flask import Blueprint, render_template, session, redirect, url_for | ||||
| 
 | ||||
| from app import db | ||||
| from app.models import User | ||||
| from app.forms import RegisterForm, LoginForm | ||||
| 
 | ||||
| auth_bp = Blueprint('auth', __name__) | ||||
| 
 | ||||
| 
 | ||||
| @auth_bp.route('/auth/register', methods=['GET', 'POST']) | ||||
| def register(): | ||||
|     """Register a new user""" | ||||
|     # validate form | ||||
|     form = RegisterForm() | ||||
|     if not form.validate_on_submit(): | ||||
|         return render_template('auth/register.html', form=form) | ||||
|     # create user | ||||
|     user = User() | ||||
|     form.populate_obj(user) | ||||
|     db.session.add(user) | ||||
|     db.session.commit() | ||||
|     # set session | ||||
|     session['auth'] = user.id | ||||
|     return redirect(url_for('index')) | ||||
| 
 | ||||
| @auth_bp.route('/auth/login', methods=['GET', 'POST']) | ||||
| def login(): | ||||
|     """Login a user""" | ||||
|     # validate form | ||||
|     form = LoginForm() | ||||
|     if not form.validate_on_submit(): | ||||
|         return render_template('auth/login.html', form=form) | ||||
|     # fetch user | ||||
|     user = User.query.filter_by(name=form.name.data).first() | ||||
|     # authenticate | ||||
|     if not (user and user.authenticate(form.password.data)): | ||||
|         return render_template('auth/login.html') | ||||
|     # set session | ||||
|     session['auth'] = user.id | ||||
|     return redirect(url_for('index')) | ||||
| 
 | ||||
| @auth_bp.route('/auth/logout') | ||||
| def logout(): | ||||
|     """Log out by clearing the session""" | ||||
|     session.clear() | ||||
|     return redirect(url_for('index')) | ||||
							
								
								
									
										13
									
								
								app/routes/map.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								app/routes/map.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,13 @@ | ||||
| """Authentication routes""" | ||||
| 
 | ||||
| from flask import Blueprint, render_template, session, redirect, url_for, \ | ||||
|     request, flash, current_app, abort, send_from_directory | ||||
| 
 | ||||
| from app import db | ||||
| 
 | ||||
| map_bp = Blueprint('map', __name__) | ||||
| 
 | ||||
| 
 | ||||
| @map_bp.route('/map') | ||||
| def map(): | ||||
|     return render_template('map.html') | ||||
							
								
								
									
										78
									
								
								app/routes/tiles.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										78
									
								
								app/routes/tiles.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,78 @@ | ||||
| """Authentication routes""" | ||||
| # pylint: disable=invalid-name | ||||
| 
 | ||||
| import os | ||||
| from PIL import Image | ||||
| from flask import Blueprint, render_template, session, redirect, url_for, \ | ||||
|     request, flash, current_app, abort, send_from_directory | ||||
| 
 | ||||
| from app import db | ||||
| from app.models import Tile | ||||
| 
 | ||||
| tiles_bp = Blueprint('tiles', __name__) | ||||
| 
 | ||||
| 
 | ||||
| def valid_tile(z, x, y): | ||||
|     """Returns true if the tile coordinates are valid""" | ||||
|     # check z in [0, 22] | ||||
|     if z < 0 or z > 22: | ||||
|         return False | ||||
|     # check x in [0, 2^zoom[ | ||||
|     if x < 0  or x >= 2**z: | ||||
|         return False | ||||
|     # check y in [0, 2^zoom[ | ||||
|     if y < 0 or y >= 2 ** z: | ||||
|         return False | ||||
|     return True | ||||
| 
 | ||||
| 
 | ||||
| @tiles_bp.route('/tiles/upload') | ||||
| def upload(): | ||||
|     """Render an upload form""" | ||||
|     return ''' | ||||
|     <!doctype html> | ||||
|     <title>Upload new File</title> | ||||
|     <h1>Upload new File</h1> | ||||
|     <form action="/tiles/1/0/1" method=post enctype=multipart/form-data> | ||||
|       <input type=file name=file> | ||||
|       <input type=submit value=Upload> | ||||
|     </form> | ||||
|     ''' | ||||
| 
 | ||||
| 
 | ||||
| @tiles_bp.route('/tiles/<int:z>/<int:x>/<int:y>', methods=['GET']) | ||||
| def get_tile(z, x, y): | ||||
|     """Serves a tile""" | ||||
|     if not valid_tile(z, x, y): | ||||
|         return abort(404) | ||||
|     folder = current_app.config['UPLOAD_FOLDER'] | ||||
|     filename = f'{z}-{x}-{y}.png' | ||||
| 
 | ||||
|     if not os.path.isfile(os.path.join(folder, filename)): | ||||
|         return send_from_directory('static', 'missing.png') | ||||
| 
 | ||||
|     return send_from_directory('../' + folder, filename) | ||||
| 
 | ||||
| 
 | ||||
| @tiles_bp.route('/tiles/<int:z>/<int:x>/<int:y>', methods=['POST']) | ||||
| def create_tile(z, x, y): | ||||
|     if not valid_tile(z, x, y): | ||||
|         return abort(404) | ||||
| 
 | ||||
|     if 'file' not in request.files: | ||||
|         flash('No file part') | ||||
|         return redirect(url_for('index')) | ||||
|     file = request.files['file'] | ||||
|     try: | ||||
|         image = Image.open(file.stream) | ||||
|         image = image.resize((256, 256)) | ||||
|     except OSError: | ||||
|         flash('cannot read image') | ||||
|         return redirect(url_for('index')) | ||||
| 
 | ||||
|     folder = current_app.config['UPLOAD_FOLDER'] | ||||
|     filename = f'{z}-{x}-{y}.png' | ||||
|     image.save(os.path.join(folder, filename)) | ||||
| 
 | ||||
|     flash('Uploaded tile successfully') | ||||
|     return redirect(url_for('index')) | ||||
							
								
								
									
										6
									
								
								app/static/bundle.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								app/static/bundle.js
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								app/static/missing.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								app/static/missing.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 4.1 KiB | 
							
								
								
									
										3
									
								
								app/static/style.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								app/static/style.css
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,3 @@ | ||||
| #map { | ||||
|     height: 60vh; | ||||
| } | ||||
							
								
								
									
										8
									
								
								app/templates/auth/login.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								app/templates/auth/login.html
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,8 @@ | ||||
| {%- from 'macros/quick_form.html' import quick_form %} | ||||
| 
 | ||||
| {% extends 'base.html' %} | ||||
| {% block body %} | ||||
| <h1>Login</h1> | ||||
| 
 | ||||
| {{ quick_form(form, url_for('auth.login')) }} | ||||
| {% endblock %} | ||||
							
								
								
									
										8
									
								
								app/templates/auth/register.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								app/templates/auth/register.html
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,8 @@ | ||||
| {%- from 'macros/quick_form.html' import quick_form %} | ||||
| 
 | ||||
| {% extends 'base.html' %} | ||||
| {% block body %} | ||||
| <h1>Register</h1> | ||||
| 
 | ||||
| {{ quick_form(form, url_for('auth.register')) }} | ||||
| {% endblock %} | ||||
							
								
								
									
										38
									
								
								app/templates/base.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								app/templates/base.html
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,38 @@ | ||||
| <!DOCTYPE html> | ||||
| <html lang="en"> | ||||
| 
 | ||||
| <head> | ||||
|     <meta charset="UTF-8"> | ||||
|     <meta name="viewport" content="width=device-width, initial-scale=1.0"> | ||||
|     <meta http-equiv="X-UA-Compatible" content="ie=edge"> | ||||
|     {% block head %}{% endblock %} | ||||
|     <title>Wundertile</title> | ||||
|     <link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}"> | ||||
| 
 | ||||
| </head> | ||||
| 
 | ||||
| <body> | ||||
|     <header> | ||||
|         <a href="{{ url_for('index') }}">Home</a> | ||||
|         {% if g.user %} | ||||
|         | <span>Hello {{ g.user.name }}</span> | ||||
|         | <a href="{{ url_for('auth.logout') }}">Logout</a> | ||||
|         {% else %} | ||||
|         | <a href="{{ url_for('auth.login') }}">Sign in</a> | ||||
|         | <a href="{{ url_for('auth.register') }}">Register</a> | ||||
|         {% endif %} | ||||
|     </header> | ||||
| 
 | ||||
| 
 | ||||
|     <hr> | ||||
| 
 | ||||
|     {% block body %}{% endblock %} | ||||
| 
 | ||||
|     <hr> | ||||
|     <h2>Messages</h2> | ||||
|     {% for message in get_flashed_messages() %} | ||||
|     <div class="flash">{{ message }}</div> | ||||
|     {% endfor %} | ||||
| </body> | ||||
| 
 | ||||
| </html> | ||||
							
								
								
									
										11
									
								
								app/templates/index.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								app/templates/index.html
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,11 @@ | ||||
| {% extends 'base.html' %} | ||||
| 
 | ||||
| 
 | ||||
| {% block body %} | ||||
| <h1>Wundertile - This is home page</h1> | ||||
| 
 | ||||
| <a href="{{ url_for('tiles.upload') }}">Upload</a><br> | ||||
| 
 | ||||
| <div id="map"></div> | ||||
| <script src="{{ url_for('static', filename='bundle.js') }}"></script> | ||||
| {% endblock %} | ||||
							
								
								
									
										26
									
								
								app/templates/macros/quick_form.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								app/templates/macros/quick_form.html
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,26 @@ | ||||
| {# macro file for printing forms #} | ||||
| 
 | ||||
| {% macro quick_form(form, dest, autofocus=True) %} | ||||
| <form action="{{ dest }}" method="post"> | ||||
|     {% for item in form if not item.label.text == "CSRF Token" %} | ||||
| 
 | ||||
|     {{ item.label }} | ||||
| 
 | ||||
|     {% if loop.first and autofocus %} | ||||
|     {{ item(autofocus=True) }} | ||||
|     {% else %} | ||||
|     {{ item }} | ||||
|     {% endif %} | ||||
| 
 | ||||
|     {% endfor %} | ||||
|     {{ form.csrf_token }} | ||||
|     <input type="submit" value="Submit"> | ||||
| </form> | ||||
| {% for field in form.errors %} | ||||
| {% for error in form.errors[field] %} | ||||
| <div class="alert alert-error"> | ||||
|     <p><strong>Error in the field {{ form[field].label.text }}!</strong> {{error}}</p> | ||||
| </div> | ||||
| {% endfor %} | ||||
| {% endfor %} | ||||
| {% endmacro %} | ||||
							
								
								
									
										62
									
								
								manage.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										62
									
								
								manage.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,62 @@ | ||||
| """App entry point""" | ||||
| 
 | ||||
| import logging.config | ||||
| import yaml | ||||
| import coverage | ||||
| import pytest | ||||
| from flask.cli import AppGroup | ||||
| 
 | ||||
| COV = coverage.coverage( | ||||
|     branch=True, | ||||
|     include='app/*', | ||||
|     omit=['tests/*'] | ||||
| ) | ||||
| COV.start() | ||||
| 
 | ||||
| from app import create_app, db | ||||
| 
 | ||||
| 
 | ||||
| # with open('logging.yml', 'r') as f: | ||||
| #     logging.config.dictConfig(yaml.load(f)) | ||||
| 
 | ||||
| 
 | ||||
| app = create_app() | ||||
| 
 | ||||
| database = AppGroup('database') | ||||
| database.help = 'Development database commands' | ||||
| 
 | ||||
| @database.command() | ||||
| def drop_db(): | ||||
|     db.drop_all() | ||||
|     db.session.commit() | ||||
| 
 | ||||
| 
 | ||||
| @database.command() | ||||
| def recreate_db(): | ||||
|     db.drop_all() | ||||
|     db.create_all() | ||||
|     db.session.commit() | ||||
| 
 | ||||
| 
 | ||||
| @database.command() | ||||
| def seed_db(): | ||||
|     seed() | ||||
| 
 | ||||
| app.cli.add_command(database) | ||||
| 
 | ||||
| 
 | ||||
| @app.cli.command() | ||||
| def test(): | ||||
|     exit(pytest.main(['-x', 'tests'])) | ||||
| 
 | ||||
| 
 | ||||
| @app.cli.command() | ||||
| def test_coverage(): | ||||
|     if pytest.main(['-x', 'tests']): | ||||
|         exit(1) | ||||
|     COV.stop() | ||||
|     COV.save() | ||||
|     print('Coverage Summary:') | ||||
|     COV.report() | ||||
|     COV.html_report() | ||||
|     COV.erase() | ||||
							
								
								
									
										30
									
								
								requirements.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								requirements.txt
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,30 @@ | ||||
| Flask==1.0.2 | ||||
| Flask-Mail==0.9.1 | ||||
| python-dotenv==0.10.1 | ||||
| PyYAML==3.13 | ||||
| itsdangerous==1.1.0 | ||||
| Flask-Bcrypt==0.7.1 | ||||
| unidecode==1.0.23 | ||||
| Pillow==6.0.0 | ||||
| 
 | ||||
| # database | ||||
| psycopg2-binary==2.7.7  | ||||
| SQLAlchemy==1.2.18 | ||||
| alembic==1.0.7 | ||||
| Flask-SQLAlchemy==2.3.2 | ||||
| Flask-Alembic==2.0.1 | ||||
| 
 | ||||
| # forms | ||||
| WTForms==2.2.1 | ||||
| Flask-WTF==0.14.2 | ||||
| 
 | ||||
| # testing | ||||
| pytest==4.3.0 | ||||
| coverage==4.5.2 | ||||
| pylint==2.2.2 | ||||
| flake8==3.7.6 | ||||
| flake8-colors==0.1.6 | ||||
| flask-debugtoolbar==0.10.1 | ||||
| 
 | ||||
| # production | ||||
| gunicorn==19.9.0 | ||||
							
								
								
									
										4
									
								
								web/.eslintrc.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								web/.eslintrc.json
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,4 @@ | ||||
| { | ||||
|     "extends": "standard" | ||||
| } | ||||
|    | ||||
							
								
								
									
										3
									
								
								web/index.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								web/index.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,3 @@ | ||||
| import './map.js' | ||||
| 
 | ||||
| console.log('Hello World') | ||||
							
								
								
									
										28
									
								
								web/map.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								web/map.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,28 @@ | ||||
| import { map as LMap, tileLayer } from 'leaflet' | ||||
| import 'leaflet/dist/leaflet.css' | ||||
| 
 | ||||
| export const map = LMap('map') | ||||
| map.setView([0, 0], 1) | ||||
| 
 | ||||
| // background tilelayer (OSM)
 | ||||
| tileLayer('https://{s}.tile.openstreetmap.de/tiles/osmde/{z}/{x}/{y}.png', { | ||||
|   maxZoom: 18, | ||||
|   attribution: '© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors', | ||||
|   opacity: 0.9 | ||||
| }).addTo(map) | ||||
| // wundertiles tilelayer
 | ||||
| tileLayer('/tiles/{z}/{x}/{y}', { | ||||
|   attribution: '', | ||||
|   maxZoom: 22 | ||||
| }).addTo(map) | ||||
| 
 | ||||
| // compute Tile Coordinates
 | ||||
| const toRad = (num) => num * Math.PI / 180 | ||||
| const getTileCoords = (lat, lon, zoom) => { | ||||
|   let xtile = parseInt(Math.floor((lon + 180) / 360 * (1 << zoom))) | ||||
|   let ytile = parseInt(Math.floor((1 - Math.log(Math.tan(toRad(lat)) + 1 / Math.cos(toRad(lat))) / Math.PI) / 2 * (1 << zoom))) | ||||
|   return { x: xtile, y: ytile, z: zoom } | ||||
| } | ||||
| 
 | ||||
| // demo: how to get tile coords on click
 | ||||
| map.on('click', (e) => { console.log(getTileCoords(e.latlng.lat, e.latlng.lng, map.getZoom())) }) | ||||
							
								
								
									
										6889
									
								
								web/package-lock.json
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										6889
									
								
								web/package-lock.json
									
									
									
										generated
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										32
									
								
								web/package.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								web/package.json
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,32 @@ | ||||
| { | ||||
|   "name": "wundertiles", | ||||
|   "version": "0.1.0", | ||||
|   "description": "", | ||||
|   "main": "index.js", | ||||
|   "scripts": { | ||||
|     "build": "webpack --mode production", | ||||
|     "lint": "eslint '**/*.js'", | ||||
|     "test": "echo \"Error: no test specified\" && exit 1" | ||||
|   }, | ||||
|   "author": "", | ||||
|   "license": "ISC", | ||||
|   "devDependencies": { | ||||
|     "css-loader": "^2.1.1", | ||||
|     "eslint": "^5.16.0", | ||||
|     "eslint-config-standard": "^12.0.0", | ||||
|     "eslint-plugin-import": "^2.17.2", | ||||
|     "eslint-plugin-node": "^8.0.1", | ||||
|     "eslint-plugin-promise": "^4.1.1", | ||||
|     "eslint-plugin-standard": "^4.0.0", | ||||
|     "less": "^3.9.0", | ||||
|     "less-loader": "^4.1.0", | ||||
|     "style-loader": "^0.23.1", | ||||
|     "url-loader": "^1.1.2", | ||||
|     "webpack": "^4.30.0", | ||||
|     "webpack-cli": "^3.3.1", | ||||
|     "webpack-dev-server": "^3.3.1" | ||||
|   }, | ||||
|   "dependencies": { | ||||
|     "leaflet": "^1.4.0" | ||||
|   } | ||||
| } | ||||
							
								
								
									
										18
									
								
								web/webpack.config.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								web/webpack.config.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,18 @@ | ||||
| const path = require('path') | ||||
| 
 | ||||
| module.exports = { | ||||
|   entry: './index.js', | ||||
|   output: { | ||||
|     filename: 'bundle.js', | ||||
|     path: path.resolve(__dirname, '../app/static') | ||||
|   }, | ||||
|   module: { | ||||
|     rules: [{ | ||||
|       test: /\.css$/, | ||||
|       use: ['style-loader', 'css-loader'] | ||||
|     }, { | ||||
|       test: /\.(png|jpg|gif)$/, | ||||
|       use: ['url-loader'] | ||||
|     }] | ||||
|   } | ||||
| } | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user