upload app
This commit is contained in:
4
CHANGES.txt
Normal file
4
CHANGES.txt
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
0.0
|
||||||
|
---
|
||||||
|
|
||||||
|
- Initial version.
|
||||||
5
MANIFEST.in
Normal file
5
MANIFEST.in
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
include *.txt *.ini *.cfg *.rst
|
||||||
|
recursive-include cleanup_html *.ico *.png *.css *.gif *.jpg *.pt *.txt *.mak *.mako *.js *.html *.xml *.jinja2
|
||||||
|
recursive-include tests *
|
||||||
|
recursive-exclude * __pycache__
|
||||||
|
recursive-exclude * *.py[co]
|
||||||
30
README.txt
Normal file
30
README.txt
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
cleanup_html
|
||||||
|
============
|
||||||
|
|
||||||
|
Getting Started
|
||||||
|
---------------
|
||||||
|
|
||||||
|
- Change directory into your newly created project if not already there. Your
|
||||||
|
current directory should be the same as this README.txt file and setup.py.
|
||||||
|
|
||||||
|
cd cleanup_html
|
||||||
|
|
||||||
|
- Create a Python virtual environment, if not already created.
|
||||||
|
|
||||||
|
python3 -m venv env
|
||||||
|
|
||||||
|
- Upgrade packaging tools, if necessary.
|
||||||
|
|
||||||
|
env/bin/pip install --upgrade pip setuptools
|
||||||
|
|
||||||
|
- Install the project in editable mode with its testing requirements.
|
||||||
|
|
||||||
|
env/bin/pip install -e ".[testing]"
|
||||||
|
|
||||||
|
- Run your project's tests.
|
||||||
|
|
||||||
|
env/bin/pytest
|
||||||
|
|
||||||
|
- Run your project.
|
||||||
|
|
||||||
|
env/bin/pserve development.ini
|
||||||
11
cleanup_html/__init__.py
Normal file
11
cleanup_html/__init__.py
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
from pyramid.config import Configurator
|
||||||
|
|
||||||
|
|
||||||
|
def main(global_config, **settings):
|
||||||
|
""" This function returns a Pyramid WSGI application.
|
||||||
|
"""
|
||||||
|
with Configurator(settings=settings) as config:
|
||||||
|
config.include('pyramid_jinja2')
|
||||||
|
config.include('.routes')
|
||||||
|
config.scan()
|
||||||
|
return config.make_wsgi_app()
|
||||||
3
cleanup_html/routes.py
Normal file
3
cleanup_html/routes.py
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
def includeme(config):
|
||||||
|
config.add_static_view('static', 'static', cache_max_age=3600)
|
||||||
|
config.add_route('home', '/')
|
||||||
BIN
cleanup_html/static/pyramid-16x16.png
Normal file
BIN
cleanup_html/static/pyramid-16x16.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.3 KiB |
BIN
cleanup_html/static/pyramid.png
Normal file
BIN
cleanup_html/static/pyramid.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 13 KiB |
32
cleanup_html/static/theme.css
Normal file
32
cleanup_html/static/theme.css
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
@import url(//fonts.googleapis.com/css?family=Open+Sans:300,400,600,700);
|
||||||
|
body {
|
||||||
|
font-family: "Open Sans", "Helvetica Neue", Helvetica, Arial, sans-serif;
|
||||||
|
font-weight: 300;
|
||||||
|
color: #1c1b1b;
|
||||||
|
background: #ffffff;
|
||||||
|
}
|
||||||
|
h1,
|
||||||
|
h2,
|
||||||
|
h3,
|
||||||
|
h4,
|
||||||
|
h5,
|
||||||
|
h6 {
|
||||||
|
font-family: "Open Sans", "Helvetica Neue", Helvetica, Arial, sans-serif;
|
||||||
|
font-weight: 300;
|
||||||
|
}
|
||||||
|
p {
|
||||||
|
font-weight: 300;
|
||||||
|
}
|
||||||
|
button, input, optgroup, select, textarea {
|
||||||
|
color: black;
|
||||||
|
}
|
||||||
|
.font-normal {
|
||||||
|
font-weight: 400;
|
||||||
|
}
|
||||||
|
.font-semi-bold {
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
.font-bold {
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
8
cleanup_html/templates/404.jinja2
Normal file
8
cleanup_html/templates/404.jinja2
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
{% extends "layout.jinja2" %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="content">
|
||||||
|
<h1><span class="font-semi-bold">Pyramid</span> <span class="smaller">Starter project</span></h1>
|
||||||
|
<p class="lead"><span class="font-semi-bold">404</span> Page Not Found</p>
|
||||||
|
</div>
|
||||||
|
{% endblock content %}
|
||||||
49
cleanup_html/templates/layout.jinja2
Normal file
49
cleanup_html/templates/layout.jinja2
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="{{request.locale_name}}">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<meta name="description" content="pyramid web application">
|
||||||
|
<meta name="author" content="Pylons Project">
|
||||||
|
<link rel="shortcut icon" href="{{request.static_url('cleanup_html:static/pyramid-16x16.png')}}">
|
||||||
|
|
||||||
|
<title>Cookiecutter Starter project for the Pyramid Web Framework</title>
|
||||||
|
|
||||||
|
<!-- Bootstrap core CSS -->
|
||||||
|
<link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
|
||||||
|
|
||||||
|
<!-- Custom styles for this scaffold -->
|
||||||
|
<link href="{{request.static_url('cleanup_html:static/theme.css')}}" rel="stylesheet">
|
||||||
|
|
||||||
|
<!-- HTML5 shiv and Respond.js IE8 support of HTML5 elements and media queries -->
|
||||||
|
<!--[if lt IE 9]>
|
||||||
|
<script src="//oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js" integrity="sha384-0s5Pv64cNZJieYFkXYOTId2HMA2Lfb6q2nAcx2n0RTLUnCAoTTsS0nKEO27XyKcY" crossorigin="anonymous"></script>
|
||||||
|
<script src="//oss.maxcdn.com/libs/respond.js/1.4.2/respond.min.js" integrity="sha384-ZoaMbDF+4LeFxg6WdScQ9nnR1QC2MIRxA1O9KWEXQwns1G8UNyIEZIQidzb0T1fo" crossorigin="anonymous"></script>
|
||||||
|
<![endif]-->
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<div class="container">
|
||||||
|
<div class="row">
|
||||||
|
{% block content %}
|
||||||
|
<p>No content</p>
|
||||||
|
{% endblock content %}
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="copyright">
|
||||||
|
Copyright © Pylons Project
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<!-- Bootstrap core JavaScript
|
||||||
|
================================================== -->
|
||||||
|
<!-- Placed at the end of the document so the pages load faster -->
|
||||||
|
<script src="//code.jquery.com/jquery-1.12.4.min.js" integrity="sha256-ZosEbRLbNQzLpnKIkEdrPv7lOy9C27hHQ+Xp8a4MxAQ=" crossorigin="anonymous"></script>
|
||||||
|
<script src="//maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
8
cleanup_html/templates/mytemplate.jinja2
Normal file
8
cleanup_html/templates/mytemplate.jinja2
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
{% extends "layout.jinja2" %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="content">
|
||||||
|
<h1><span class="font-semi-bold">Pyramid</span> <span class="smaller">Starter project</span></h1>
|
||||||
|
<p class="lead">Welcome to <span class="font-normal">{{project}}</span>, a Pyramid application generated by<br><span class="font-normal">Cookiecutter</span>.</p>
|
||||||
|
</div>
|
||||||
|
{% endblock content %}
|
||||||
0
cleanup_html/views/__init__.py
Normal file
0
cleanup_html/views/__init__.py
Normal file
6
cleanup_html/views/default.py
Normal file
6
cleanup_html/views/default.py
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
from pyramid.view import view_config
|
||||||
|
|
||||||
|
|
||||||
|
@view_config(route_name='home', renderer='cleanup_html:templates/mytemplate.jinja2')
|
||||||
|
def my_view(request):
|
||||||
|
return {'project': 'cleanup_html'}
|
||||||
7
cleanup_html/views/notfound.py
Normal file
7
cleanup_html/views/notfound.py
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
from pyramid.view import notfound_view_config
|
||||||
|
|
||||||
|
|
||||||
|
@notfound_view_config(renderer='cleanup_html:templates/404.jinja2')
|
||||||
|
def notfound_view(request):
|
||||||
|
request.response.status = 404
|
||||||
|
return {}
|
||||||
59
development.ini
Normal file
59
development.ini
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
###
|
||||||
|
# app configuration
|
||||||
|
# https://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html
|
||||||
|
###
|
||||||
|
|
||||||
|
[app:main]
|
||||||
|
use = egg:cleanup_html
|
||||||
|
|
||||||
|
pyramid.reload_templates = true
|
||||||
|
pyramid.debug_authorization = false
|
||||||
|
pyramid.debug_notfound = false
|
||||||
|
pyramid.debug_routematch = false
|
||||||
|
pyramid.default_locale_name = en
|
||||||
|
pyramid.includes =
|
||||||
|
pyramid_debugtoolbar
|
||||||
|
|
||||||
|
# By default, the toolbar only appears for clients from IP addresses
|
||||||
|
# '127.0.0.1' and '::1'.
|
||||||
|
# debugtoolbar.hosts = 127.0.0.1 ::1
|
||||||
|
|
||||||
|
###
|
||||||
|
# wsgi server configuration
|
||||||
|
###
|
||||||
|
|
||||||
|
[server:main]
|
||||||
|
use = egg:waitress#main
|
||||||
|
listen = localhost:6543
|
||||||
|
|
||||||
|
###
|
||||||
|
# logging configuration
|
||||||
|
# https://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html
|
||||||
|
###
|
||||||
|
|
||||||
|
[loggers]
|
||||||
|
keys = root, cleanup_html
|
||||||
|
|
||||||
|
[handlers]
|
||||||
|
keys = console
|
||||||
|
|
||||||
|
[formatters]
|
||||||
|
keys = generic
|
||||||
|
|
||||||
|
[logger_root]
|
||||||
|
level = INFO
|
||||||
|
handlers = console
|
||||||
|
|
||||||
|
[logger_cleanup_html]
|
||||||
|
level = DEBUG
|
||||||
|
handlers =
|
||||||
|
qualname = cleanup_html
|
||||||
|
|
||||||
|
[handler_console]
|
||||||
|
class = StreamHandler
|
||||||
|
args = (sys.stderr,)
|
||||||
|
level = NOTSET
|
||||||
|
formatter = generic
|
||||||
|
|
||||||
|
[formatter_generic]
|
||||||
|
format = %(asctime)s %(levelname)-5.5s [%(name)s:%(lineno)s][%(threadName)s] %(message)s
|
||||||
53
production.ini
Normal file
53
production.ini
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
###
|
||||||
|
# app configuration
|
||||||
|
# https://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html
|
||||||
|
###
|
||||||
|
|
||||||
|
[app:main]
|
||||||
|
use = egg:cleanup_html
|
||||||
|
|
||||||
|
pyramid.reload_templates = false
|
||||||
|
pyramid.debug_authorization = false
|
||||||
|
pyramid.debug_notfound = false
|
||||||
|
pyramid.debug_routematch = false
|
||||||
|
pyramid.default_locale_name = en
|
||||||
|
|
||||||
|
###
|
||||||
|
# wsgi server configuration
|
||||||
|
###
|
||||||
|
|
||||||
|
[server:main]
|
||||||
|
use = egg:waitress#main
|
||||||
|
listen = *:6543
|
||||||
|
|
||||||
|
###
|
||||||
|
# logging configuration
|
||||||
|
# https://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html
|
||||||
|
###
|
||||||
|
|
||||||
|
[loggers]
|
||||||
|
keys = root, cleanup_html
|
||||||
|
|
||||||
|
[handlers]
|
||||||
|
keys = console
|
||||||
|
|
||||||
|
[formatters]
|
||||||
|
keys = generic
|
||||||
|
|
||||||
|
[logger_root]
|
||||||
|
level = WARN
|
||||||
|
handlers = console
|
||||||
|
|
||||||
|
[logger_cleanup_html]
|
||||||
|
level = WARN
|
||||||
|
handlers =
|
||||||
|
qualname = cleanup_html
|
||||||
|
|
||||||
|
[handler_console]
|
||||||
|
class = StreamHandler
|
||||||
|
args = (sys.stderr,)
|
||||||
|
level = NOTSET
|
||||||
|
formatter = generic
|
||||||
|
|
||||||
|
[formatter_generic]
|
||||||
|
format = %(asctime)s %(levelname)-5.5s [%(name)s:%(lineno)s][%(threadName)s] %(message)s
|
||||||
6
pytest.ini
Normal file
6
pytest.ini
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
[pytest]
|
||||||
|
addopts = --strict-markers
|
||||||
|
|
||||||
|
testpaths =
|
||||||
|
cleanup_html
|
||||||
|
tests
|
||||||
52
setup.py
Normal file
52
setup.py
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
import os
|
||||||
|
|
||||||
|
from setuptools import setup, find_packages
|
||||||
|
|
||||||
|
here = os.path.abspath(os.path.dirname(__file__))
|
||||||
|
with open(os.path.join(here, 'README.txt')) as f:
|
||||||
|
README = f.read()
|
||||||
|
with open(os.path.join(here, 'CHANGES.txt')) as f:
|
||||||
|
CHANGES = f.read()
|
||||||
|
|
||||||
|
requires = [
|
||||||
|
'plaster_pastedeploy',
|
||||||
|
'pyramid',
|
||||||
|
'pyramid_jinja2',
|
||||||
|
'pyramid_debugtoolbar',
|
||||||
|
'waitress',
|
||||||
|
]
|
||||||
|
|
||||||
|
tests_require = [
|
||||||
|
'WebTest',
|
||||||
|
'pytest',
|
||||||
|
'pytest-cov',
|
||||||
|
]
|
||||||
|
|
||||||
|
setup(
|
||||||
|
name='cleanup_html',
|
||||||
|
version='0.0',
|
||||||
|
description='cleanup_html',
|
||||||
|
long_description=README + '\n\n' + CHANGES,
|
||||||
|
classifiers=[
|
||||||
|
'Programming Language :: Python',
|
||||||
|
'Framework :: Pyramid',
|
||||||
|
'Topic :: Internet :: WWW/HTTP',
|
||||||
|
'Topic :: Internet :: WWW/HTTP :: WSGI :: Application',
|
||||||
|
],
|
||||||
|
author='',
|
||||||
|
author_email='',
|
||||||
|
url='',
|
||||||
|
keywords='web pyramid pylons',
|
||||||
|
packages=find_packages(exclude=['tests']),
|
||||||
|
include_package_data=True,
|
||||||
|
zip_safe=False,
|
||||||
|
extras_require={
|
||||||
|
'testing': tests_require,
|
||||||
|
},
|
||||||
|
install_requires=requires,
|
||||||
|
entry_points={
|
||||||
|
'paste.app_factory': [
|
||||||
|
'main = cleanup_html:main',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
)
|
||||||
53
testing.ini
Normal file
53
testing.ini
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
###
|
||||||
|
# app configuration
|
||||||
|
# https://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html
|
||||||
|
###
|
||||||
|
|
||||||
|
[app:main]
|
||||||
|
use = egg:cleanup_html
|
||||||
|
|
||||||
|
pyramid.reload_templates = false
|
||||||
|
pyramid.debug_authorization = false
|
||||||
|
pyramid.debug_notfound = false
|
||||||
|
pyramid.debug_routematch = false
|
||||||
|
pyramid.default_locale_name = en
|
||||||
|
|
||||||
|
###
|
||||||
|
# wsgi server configuration
|
||||||
|
###
|
||||||
|
|
||||||
|
[server:main]
|
||||||
|
use = egg:waitress#main
|
||||||
|
listen = localhost:6543
|
||||||
|
|
||||||
|
###
|
||||||
|
# logging configuration
|
||||||
|
# https://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html
|
||||||
|
###
|
||||||
|
|
||||||
|
[loggers]
|
||||||
|
keys = root, cleanup_html
|
||||||
|
|
||||||
|
[handlers]
|
||||||
|
keys = console
|
||||||
|
|
||||||
|
[formatters]
|
||||||
|
keys = generic
|
||||||
|
|
||||||
|
[logger_root]
|
||||||
|
level = INFO
|
||||||
|
handlers = console
|
||||||
|
|
||||||
|
[logger_cleanup_html]
|
||||||
|
level = DEBUG
|
||||||
|
handlers =
|
||||||
|
qualname = cleanup_html
|
||||||
|
|
||||||
|
[handler_console]
|
||||||
|
class = StreamHandler
|
||||||
|
args = (sys.stderr,)
|
||||||
|
level = NOTSET
|
||||||
|
formatter = generic
|
||||||
|
|
||||||
|
[formatter_generic]
|
||||||
|
format = %(asctime)s %(levelname)-5.5s [%(name)s:%(lineno)s][%(threadName)s] %(message)s
|
||||||
0
tests/__init__.py
Normal file
0
tests/__init__.py
Normal file
76
tests/conftest.py
Normal file
76
tests/conftest.py
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
import os
|
||||||
|
from pyramid.paster import get_appsettings
|
||||||
|
from pyramid.scripting import prepare
|
||||||
|
from pyramid.testing import DummyRequest, testConfig
|
||||||
|
import pytest
|
||||||
|
import webtest
|
||||||
|
|
||||||
|
from cleanup_html import main
|
||||||
|
|
||||||
|
|
||||||
|
def pytest_addoption(parser):
|
||||||
|
parser.addoption('--ini', action='store', metavar='INI_FILE')
|
||||||
|
|
||||||
|
@pytest.fixture(scope='session')
|
||||||
|
def ini_file(request):
|
||||||
|
# potentially grab this path from a pytest option
|
||||||
|
return os.path.abspath(request.config.option.ini or 'testing.ini')
|
||||||
|
|
||||||
|
@pytest.fixture(scope='session')
|
||||||
|
def app_settings(ini_file):
|
||||||
|
return get_appsettings(ini_file)
|
||||||
|
|
||||||
|
@pytest.fixture(scope='session')
|
||||||
|
def app(app_settings):
|
||||||
|
return main({}, **app_settings)
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def testapp(app):
|
||||||
|
testapp = webtest.TestApp(app, extra_environ={
|
||||||
|
'HTTP_HOST': 'example.com',
|
||||||
|
})
|
||||||
|
|
||||||
|
return testapp
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def app_request(app):
|
||||||
|
"""
|
||||||
|
A real request.
|
||||||
|
|
||||||
|
This request is almost identical to a real request but it has some
|
||||||
|
drawbacks in tests as it's harder to mock data and is heavier.
|
||||||
|
|
||||||
|
"""
|
||||||
|
with prepare(registry=app.registry) as env:
|
||||||
|
request = env['request']
|
||||||
|
request.host = 'example.com'
|
||||||
|
yield request
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def dummy_request():
|
||||||
|
"""
|
||||||
|
A lightweight dummy request.
|
||||||
|
|
||||||
|
This request is ultra-lightweight and should be used only when the request
|
||||||
|
itself is not a large focus in the call-stack. It is much easier to mock
|
||||||
|
and control side-effects using this object, however:
|
||||||
|
|
||||||
|
- It does not have request extensions applied.
|
||||||
|
- Threadlocals are not properly pushed.
|
||||||
|
|
||||||
|
"""
|
||||||
|
request = DummyRequest()
|
||||||
|
request.host = 'example.com'
|
||||||
|
|
||||||
|
return request
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def dummy_config(dummy_request):
|
||||||
|
"""
|
||||||
|
A dummy :class:`pyramid.config.Configurator` object. This allows for
|
||||||
|
mock configuration, including configuration for ``dummy_request``, as well
|
||||||
|
as pushing the appropriate threadlocals.
|
||||||
|
|
||||||
|
"""
|
||||||
|
with testConfig(request=dummy_request) as config:
|
||||||
|
yield config
|
||||||
7
tests/test_functional.py
Normal file
7
tests/test_functional.py
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
def test_root(testapp):
|
||||||
|
res = testapp.get('/', status=200)
|
||||||
|
assert b'Pyramid' in res.body
|
||||||
|
|
||||||
|
def test_notfound(testapp):
|
||||||
|
res = testapp.get('/badurl', status=404)
|
||||||
|
assert res.status_code == 404
|
||||||
13
tests/test_views.py
Normal file
13
tests/test_views.py
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
from cleanup_html.views.default import my_view
|
||||||
|
from cleanup_html.views.notfound import notfound_view
|
||||||
|
|
||||||
|
|
||||||
|
def test_my_view(app_request):
|
||||||
|
info = my_view(app_request)
|
||||||
|
assert app_request.response.status_int == 200
|
||||||
|
assert info['project'] == 'cleanup_html'
|
||||||
|
|
||||||
|
def test_notfound_view(app_request):
|
||||||
|
info = notfound_view(app_request)
|
||||||
|
assert app_request.response.status_int == 404
|
||||||
|
assert info == {}
|
||||||
Reference in New Issue
Block a user