From 40b628f4c09d7cc33b4c6f5c1e6f00cfc73aca4c Mon Sep 17 00:00:00 2001 From: yunica Date: Fri, 9 Dec 2022 11:11:49 -0500 Subject: [PATCH 1/3] update requirements & gitignore --- .gitignore | 7 +++++++ requirements/base.txt | 36 ++++++++++++++++++------------------ requirements/local.txt | 8 ++++---- 3 files changed, 29 insertions(+), 22 deletions(-) diff --git a/.gitignore b/.gitignore index 006773dd..ff4997b3 100644 --- a/.gitignore +++ b/.gitignore @@ -44,3 +44,10 @@ Desktop.ini #js node_modules/ + + +# Pycharm +.idea/ + +# Environment +.env \ No newline at end of file diff --git a/requirements/base.txt b/requirements/base.txt index b7f544d1..b7220bcf 100644 --- a/requirements/base.txt +++ b/requirements/base.txt @@ -1,48 +1,48 @@ # Bleeding edge Django -django==2.2.28 +django==3.2.15 # Configuration -django-environ==0.4.5 +django-environ==0.9.0 # Forms -django-braces==1.14.0 +django-braces==1.15.0 # Models django-model-utils==4.2.0 # For authentication with OSM -social-auth-app-django==4.0.0 -social-auth-core==3.3.3 +social-auth-app-django==5.0.0 +social-auth-core==4.3.0 # DRF and DRF-gis -djangorestframework==3.11.2 -djangorestframework-gis==0.17 +djangorestframework==3.13.1 +djangorestframework-gis==1.0 djangorestframework-csv==2.1.1 -django-filter==2.4.0 -django-cors-headers==3.7.0 -drf-yasg==1.20.0 +django-filter==22.1 +django-cors-headers==3.13.0 +drf-yasg==1.21.3 # For the persistence stores -psycopg2-binary==2.8.6 +psycopg2-binary==2.9.3 # Unicode slugification -unicode-slugify==0.1.3 +unicode-slugify==0.1.5 django-autoslug==1.9.8 # Time zones support -pytz==2021.1 +pytz==2022.2.1 # Celery support -celery==3.1.25 +celery==5.2.7 # Redis support -django-redis==4.11.0 -redis>=2.10.5 +django-redis==5.2.0 +redis==4.3.4 # Your custom requirements go here -PyYAML==5.4.1 +PyYAML==6.0 osmcha==0.8.6 # git python is required by the frontend management command -GitPython==3.1.18 +GitPython==3.1.27 diff --git a/requirements/local.txt b/requirements/local.txt index e4582ee0..1805e093 100644 --- a/requirements/local.txt +++ b/requirements/local.txt @@ -1,12 +1,12 @@ # Local development dependencies go here -r test.txt -django-extensions==3.1.5 +django-extensions==3.2.0 Sphinx==4.3.2 -Werkzeug==1.0.1 +Werkzeug==2.2.2 # django-debug-toolbar that works with Django 1.5+ -django-debug-toolbar==3.2.4 +django-debug-toolbar==3.5.0 sqlparse==0.4.2 #shell -bpython==0.21 +bpython==0.22.1 From b22337f8777b80d1031ae4c1d3373249ef7c4f6c Mon Sep 17 00:00:00 2001 From: yunica Date: Fri, 9 Dec 2022 12:35:03 -0500 Subject: [PATCH 2/3] add apps file --- config/settings/common.py | 320 +++++++++++----------- manage.py | 18 +- osmchadjango/changeset/apps.py | 6 + osmchadjango/feature/apps.py | 6 + osmchadjango/frontend/apps.py | 3 +- osmchadjango/roulette_integration/apps.py | 4 +- osmchadjango/supervise/apps.py | 4 +- osmchadjango/users/apps.py | 6 + 8 files changed, 205 insertions(+), 162 deletions(-) create mode 100644 osmchadjango/changeset/apps.py create mode 100644 osmchadjango/feature/apps.py create mode 100644 osmchadjango/users/apps.py diff --git a/config/settings/common.py b/config/settings/common.py index 189e33f3..c0cc64fa 100644 --- a/config/settings/common.py +++ b/config/settings/common.py @@ -8,66 +8,62 @@ For the full list of settings and their values, see https://docs.djangoproject.com/en/dev/ref/settings/ """ -from __future__ import absolute_import, unicode_literals - import environ import os - +from slugify import slugify ROOT_DIR = environ.Path(__file__) - 3 # (/a/b/myfile.py - 3 = /) -APPS_DIR = ROOT_DIR.path('osmchadjango') +APPS_DIR = ROOT_DIR.path("osmchadjango") env = environ.Env() # .env file, should load only in development environment -READ_DOT_ENV_FILE = env('DJANGO_READ_DOT_ENV_FILE', default=False) +READ_DOT_ENV_FILE = env("DJANGO_READ_DOT_ENV_FILE", default=False) if READ_DOT_ENV_FILE: # Operating System Environment variables have precedence over variables defined in the .env file, # that is to say variables from the .env files will only be used if not defined # as environment variables. - env_file = str(ROOT_DIR.path('.env')) - print('Loading : {}'.format(env_file)) + env_file = str(ROOT_DIR.path(".env")) + print("Loading : {}".format(env_file)) env.read_env(env_file) - print('The .env file has been loaded. See common.py for more information') + print("The .env file has been loaded. See common.py for more information") # APP CONFIGURATION # ------------------------------------------------------------------------------ DJANGO_APPS = ( # Default Django apps: - 'django.contrib.auth', - 'django.contrib.contenttypes', - 'django.contrib.sessions', - 'django.contrib.sites', - 'django.contrib.messages', - 'django.contrib.staticfiles', - 'django.contrib.gis', - + "django.contrib.auth", + "django.contrib.contenttypes", + "django.contrib.sessions", + "django.contrib.sites", + "django.contrib.messages", + "django.contrib.staticfiles", + "django.contrib.gis", # Useful template tags: # 'django.contrib.humanize', - # Admin - 'django.contrib.admin', + "django.contrib.admin", ) THIRD_PARTY_APPS = ( - 'rest_framework', - 'rest_framework_gis', - 'rest_framework.authtoken', - 'social_django', - 'corsheaders', - 'django_filters', - 'drf_yasg', + "rest_framework", + "rest_framework_gis", + "rest_framework.authtoken", + "social_django", + "corsheaders", + "django_filters", + "drf_yasg", ) # Apps specific for this project go here. LOCAL_APPS = ( - 'osmchadjango.users', # custom users app - 'osmchadjango.changeset', - 'osmchadjango.feature', - 'osmchadjango.supervise', - 'osmchadjango.frontend', - 'osmchadjango.roulette_integration', + "osmchadjango.users.apps.UsersConfig", # custom users app + "osmchadjango.changeset.apps.ChangeSetConfig", + "osmchadjango.feature.apps.FeatureConfig", + "osmchadjango.supervise.apps.SuperviseConfig", + "osmchadjango.frontend.apps.FrontendConfig", + "osmchadjango.roulette_integration.apps.RouletteIntegrationConfig", ) # See: https://docs.djangoproject.com/en/dev/ref/settings/#installed-apps @@ -76,23 +72,21 @@ # MIDDLEWARE CONFIGURATION # ------------------------------------------------------------------------------ MIDDLEWARE = [ - 'django.middleware.security.SecurityMiddleware', - 'django.contrib.sessions.middleware.SessionMiddleware', - 'django.middleware.gzip.GZipMiddleware', - 'corsheaders.middleware.CorsMiddleware', - 'django.middleware.common.CommonMiddleware', - 'django.middleware.csrf.CsrfViewMiddleware', - 'django.middleware.http.ConditionalGetMiddleware', - 'django.contrib.auth.middleware.AuthenticationMiddleware', - 'django.contrib.messages.middleware.MessageMiddleware', - 'django.middleware.clickjacking.XFrameOptionsMiddleware' + "django.middleware.security.SecurityMiddleware", + "django.contrib.sessions.middleware.SessionMiddleware", + "django.middleware.gzip.GZipMiddleware", + "corsheaders.middleware.CorsMiddleware", + "django.middleware.common.CommonMiddleware", + "django.middleware.csrf.CsrfViewMiddleware", + "django.middleware.http.ConditionalGetMiddleware", + "django.contrib.auth.middleware.AuthenticationMiddleware", + "django.contrib.messages.middleware.MessageMiddleware", + "django.middleware.clickjacking.XFrameOptionsMiddleware", ] # MIGRATIONS CONFIGURATION # ------------------------------------------------------------------------------ -MIGRATION_MODULES = { - 'sites': 'osmchadjango.contrib.sites.migrations' -} +MIGRATION_MODULES = {"sites": "osmchadjango.contrib.sites.migrations"} # DEBUG # ------------------------------------------------------------------------------ @@ -102,20 +96,18 @@ # FIXTURE CONFIGURATION # ------------------------------------------------------------------------------ # See: https://docs.djangoproject.com/en/dev/ref/settings/#std:setting-FIXTURE_DIRS -FIXTURE_DIRS = ( - str(APPS_DIR.path('fixtures')), -) +FIXTURE_DIRS = (str(APPS_DIR.path("fixtures")),) # EMAIL CONFIGURATION # ------------------------------------------------------------------------------ -EMAIL_BACKEND = env('DJANGO_EMAIL_BACKEND', default='django.core.mail.backends.smtp.EmailBackend') +EMAIL_BACKEND = env( + "DJANGO_EMAIL_BACKEND", default="django.core.mail.backends.smtp.EmailBackend" +) # MANAGER CONFIGURATION # ------------------------------------------------------------------------------ # See: https://docs.djangoproject.com/en/dev/ref/settings/#admins -ADMINS = ( - ('name', 'email@email.com'), -) +ADMINS = (("name", "email@email.com"),) # See: https://docs.djangoproject.com/en/dev/ref/settings/#managers MANAGERS = ADMINS @@ -127,15 +119,15 @@ # 'default': env.db('DATABASE_URL', default='postgres:///osmcha'), # } DATABASES = { - 'default': { - 'ENGINE': 'django.contrib.gis.db.backends.postgis', - 'NAME': 'osmcha', - 'USER': env('POSTGRES_USER'), - 'PASSWORD': env('POSTGRES_PASSWORD'), - 'HOST': env('PGHOST', default='localhost') - } + "default": { + "ENGINE": "django.contrib.gis.db.backends.postgis", + "NAME": "osmcha", + "USER": env("POSTGRES_USER"), + "PASSWORD": env("POSTGRES_PASSWORD"), + "HOST": env("PGHOST", default="localhost"), + } } -DATABASES['default']['ATOMIC_REQUESTS'] = True +DATABASES["default"]["ATOMIC_REQUESTS"] = True # GENERAL CONFIGURATION @@ -144,10 +136,10 @@ # http://en.wikipedia.org/wiki/List_of_tz_zones_by_name # although not all choices may be available on all operating systems. # In a Windows environment this must be set to your system time zone. -TIME_ZONE = 'UTC' +TIME_ZONE = "UTC" # See: https://docs.djangoproject.com/en/dev/ref/settings/#language-code -LANGUAGE_CODE = 'en-us' +LANGUAGE_CODE = "en-us" # See: https://docs.djangoproject.com/en/dev/ref/settings/#site-id SITE_ID = 1 @@ -167,32 +159,32 @@ TEMPLATES = [ { # See: https://docs.djangoproject.com/en/dev/ref/settings/#std:setting-TEMPLATES-BACKEND - 'BACKEND': 'django.template.backends.django.DjangoTemplates', + "BACKEND": "django.template.backends.django.DjangoTemplates", # See: https://docs.djangoproject.com/en/dev/ref/settings/#template-dirs - 'DIRS': [ - str(APPS_DIR.path('templates')), + "DIRS": [ + str(APPS_DIR.path("templates")), ], - 'OPTIONS': { + "OPTIONS": { # See: https://docs.djangoproject.com/en/dev/ref/settings/#template-debug - 'debug': DEBUG, + "debug": DEBUG, # See: https://docs.djangoproject.com/en/dev/ref/settings/#template-loaders # https://docs.djangoproject.com/en/dev/ref/templates/api/#loader-types - 'loaders': [ - 'django.template.loaders.filesystem.Loader', - 'django.template.loaders.app_directories.Loader', + "loaders": [ + "django.template.loaders.filesystem.Loader", + "django.template.loaders.app_directories.Loader", ], # See: https://docs.djangoproject.com/en/dev/ref/settings/#template-context-processors - 'context_processors': [ - 'django.template.context_processors.debug', - 'django.template.context_processors.request', - 'django.contrib.auth.context_processors.auth', - 'django.template.context_processors.i18n', - 'django.template.context_processors.media', - 'django.template.context_processors.static', - 'django.template.context_processors.tz', - 'django.contrib.messages.context_processors.messages', - 'social_django.context_processors.backends', - 'social_django.context_processors.login_redirect', + "context_processors": [ + "django.template.context_processors.debug", + "django.template.context_processors.request", + "django.contrib.auth.context_processors.auth", + "django.template.context_processors.i18n", + "django.template.context_processors.media", + "django.template.context_processors.static", + "django.template.context_processors.tz", + "django.contrib.messages.context_processors.messages", + "social_django.context_processors.backends", + "social_django.context_processors.login_redirect", # Your stuff: custom template context processors go here ], }, @@ -200,44 +192,42 @@ ] # See: http://django-crispy-forms.readthedocs.org/en/latest/install.html#template-packs -CRISPY_TEMPLATE_PACK = 'bootstrap3' +CRISPY_TEMPLATE_PACK = "bootstrap3" # STATIC FILE CONFIGURATION # ------------------------------------------------------------------------------ # See: https://docs.djangoproject.com/en/dev/ref/settings/#static-root -STATIC_ROOT = str(ROOT_DIR('staticfiles')) +STATIC_ROOT = str(ROOT_DIR("staticfiles")) # See: https://docs.djangoproject.com/en/dev/ref/settings/#static-url -STATIC_URL = '/static/' +STATIC_URL = "/static/" # See: https://docs.djangoproject.com/en/dev/ref/contrib/staticfiles/#std:setting-STATICFILES_DIRS -STATICFILES_DIRS = ( - str(APPS_DIR.path('static')), -) +STATICFILES_DIRS = (str(APPS_DIR.path("static")),) # See: https://docs.djangoproject.com/en/dev/ref/contrib/staticfiles/#staticfiles-finders STATICFILES_FINDERS = ( - 'django.contrib.staticfiles.finders.FileSystemFinder', - 'django.contrib.staticfiles.finders.AppDirectoriesFinder', + "django.contrib.staticfiles.finders.FileSystemFinder", + "django.contrib.staticfiles.finders.AppDirectoriesFinder", ) # MEDIA CONFIGURATION # ------------------------------------------------------------------------------ # See: https://docs.djangoproject.com/en/dev/ref/settings/#media-root -MEDIA_ROOT = str(APPS_DIR('media')) +MEDIA_ROOT = str(APPS_DIR("media")) # See: https://docs.djangoproject.com/en/dev/ref/settings/#media-url -MEDIA_URL = '/media/' +MEDIA_URL = "/media/" # URL Configuration # ------------------------------------------------------------------------------ -ROOT_URLCONF = 'config.urls' +ROOT_URLCONF = "config.urls" # See: https://docs.djangoproject.com/en/dev/ref/settings/#wsgi-application -WSGI_APPLICATION = 'config.wsgi.application' +WSGI_APPLICATION = "config.wsgi.application" # CELERY CONFIGURATION -BROKER_URL = env('CELERY_BROKER_URL', default='redis://localhost:6379/0') +BROKER_URL = env("CELERY_BROKER_URL", default="redis://localhost:6379/0") # Some really nice defaults # ACCOUNT_AUTHENTICATION_METHOD = 'username' @@ -246,36 +236,54 @@ # Custom user app defaults # Select the correct user model -AUTH_USER_MODEL = 'users.User' +AUTH_USER_MODEL = "users.User" # SLUGLIFIER -AUTOSLUG_SLUGIFY_FUNCTION = 'slugify.slugify' +AUTOSLUG_SLUGIFY_FUNCTION = "slugify.slugify" # SOCIAL AUTH CONFIGURATION SOCIAL_AUTH_DEFAULT_USERNAME = lambda u: slugify(u) SOCIAL_AUTH_ASSOCIATE_BY_EMAIL = True -SOCIAL_AUTH_OPENSTREETMAP_KEY = env('OAUTH_OSM_KEY', default='') -SOCIAL_AUTH_OPENSTREETMAP_SECRET = env('OAUTH_OSM_SECRET', default='') +SOCIAL_AUTH_OPENSTREETMAP_KEY = env("OAUTH_OSM_KEY", default="") +SOCIAL_AUTH_OPENSTREETMAP_SECRET = env("OAUTH_OSM_SECRET", default="") + +# AUTH PASSWORD VALIDATORS +# ------------------------------------------------------------------------------ +# https://docs.djangoproject.com/en/dev/ref/settings/#auth-password-validators +AUTH_PASSWORD_VALIDATORS = [ + { + "NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator", + }, + { + "NAME": "django.contrib.auth.password_validation.MinimumLengthValidator", + }, + { + "NAME": "django.contrib.auth.password_validation.CommonPasswordValidator", + }, + { + "NAME": "django.contrib.auth.password_validation.NumericPasswordValidator", + }, +] # AUTHENTICATION CONFIGURATION # ------------------------------------------------------------------------------ AUTHENTICATION_BACKENDS = ( - 'social_core.backends.openstreetmap.OpenStreetMapOAuth', - 'django.contrib.auth.backends.ModelBackend', + "social_core.backends.openstreetmap.OpenStreetMapOAuth", + "django.contrib.auth.backends.ModelBackend", ) SOCIAL_AUTH_PIPELINE = ( - 'social_core.pipeline.social_auth.social_details', - 'social_core.pipeline.social_auth.social_uid', - 'social_core.pipeline.social_auth.auth_allowed', - 'social_core.pipeline.social_auth.social_user', - 'social_core.pipeline.user.get_username', - 'social_core.pipeline.user.create_user', - 'osmchadjango.users.utils.save_real_username', - 'social_core.pipeline.social_auth.associate_user', - 'social_core.pipeline.social_auth.load_extra_data', - 'social_core.pipeline.user.user_details' + "social_core.pipeline.social_auth.social_details", + "social_core.pipeline.social_auth.social_uid", + "social_core.pipeline.social_auth.auth_allowed", + "social_core.pipeline.social_auth.social_user", + "social_core.pipeline.user.get_username", + "social_core.pipeline.user.create_user", + "osmchadjango.users.utils.save_real_username", + "social_core.pipeline.social_auth.associate_user", + "social_core.pipeline.social_auth.load_extra_data", + "social_core.pipeline.user.user_details" # 'social_core.pipeline.social_auth.associate_by_email', ) @@ -289,21 +297,23 @@ # more details on how to customize your logging configuration. LOGGING = { - 'version': 1, - 'disable_existing_loggers': False, - 'handlers': { - 'console': { - 'class': 'logging.StreamHandler', + "version": 1, + "disable_existing_loggers": False, + "handlers": { + "console": { + "class": "logging.StreamHandler", }, }, - 'loggers': { - 'django': { - 'handlers': ['console'], - 'level': os.getenv('DJANGO_LOG_LEVEL', 'INFO'), + "loggers": { + "django": { + "handlers": ["console"], + "level": os.getenv("DJANGO_LOG_LEVEL", "INFO"), }, - 'osmchadjango.users': { - 'handlers': ['console', ], - 'level': "DEBUG", + "osmchadjango.users": { + "handlers": [ + "console", + ], + "level": "DEBUG", }, }, } @@ -312,46 +322,40 @@ # If you want to filter the import of changesets to a defined area of the world, # define CHANGESETS_FILTER as a path to a GeoJSON file. -CHANGESETS_FILTER = env('DJANGO_CHANGESETS_FILTER', default=None) +CHANGESETS_FILTER = env("DJANGO_CHANGESETS_FILTER", default=None) # Enable/disable the functionality to post comments to changesets when they # are reviewed -ENABLE_POST_CHANGESET_COMMENTS = env('DJANGO_ENABLE_CHANGESET_COMMENTS', default=False) +ENABLE_POST_CHANGESET_COMMENTS = env("DJANGO_ENABLE_CHANGESET_COMMENTS", default=False) # Your common stuff: Below this line define 3rd party library settings REST_FRAMEWORK = { - 'DEFAULT_AUTHENTICATION_CLASSES': ( - 'rest_framework.authentication.BasicAuthentication', - 'rest_framework.authentication.TokenAuthentication', - 'rest_framework.authentication.SessionAuthentication', - ), - 'DEFAULT_SCHEMA_CLASS': 'rest_framework.schemas.coreapi.AutoSchema', - 'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination', - 'PAGE_SIZE': 50, - 'DEFAULT_FILTER_BACKENDS': ( - 'django_filters.rest_framework.DjangoFilterBackend', - ), - 'TEST_REQUEST_DEFAULT_FORMAT': 'json', - 'DEFAULT_THROTTLE_RATES': { - 'non_staff_user': env('NON_STAFF_USER_THROTTLE_RATE', default='3/min') - }, - 'ORDERING_PARAM': 'order_by', - } + "DEFAULT_AUTHENTICATION_CLASSES": ( + "rest_framework.authentication.BasicAuthentication", + "rest_framework.authentication.TokenAuthentication", + "rest_framework.authentication.SessionAuthentication", + ), + "DEFAULT_SCHEMA_CLASS": "rest_framework.schemas.coreapi.AutoSchema", + "DEFAULT_PAGINATION_CLASS": "rest_framework.pagination.PageNumberPagination", + "PAGE_SIZE": 50, + "DEFAULT_FILTER_BACKENDS": ("django_filters.rest_framework.DjangoFilterBackend",), + "TEST_REQUEST_DEFAULT_FORMAT": "json", + "DEFAULT_THROTTLE_RATES": { + "non_staff_user": env("NON_STAFF_USER_THROTTLE_RATE", default="3/min") + }, + "ORDERING_PARAM": "order_by", +} # Allow cross domain requests CORS_ALLOW_ALL_ORIGINS = True # SWAGGER SETTINGS SWAGGER_SETTINGS = { - 'USE_SESSION_AUTH': False, - 'SECURITY_DEFINITIONS': { - 'api_key': { - 'type': 'apiKey', - 'name': 'Authorization', - 'in': 'header' - } - }, - } + "USE_SESSION_AUTH": False, + "SECURITY_DEFINITIONS": { + "api_key": {"type": "apiKey", "name": "Authorization", "in": "header"} + }, +} # CACHALOT SETTINGS CACHALOT_TIMEOUT = 180 @@ -360,15 +364,21 @@ # FRONTEND SETTINGS # ----------------------------------------------------------------------------- # Version or any valid git branch tag of front-end code -OSMCHA_FRONTEND_VERSION = env('OSMCHA_FRONTEND_VERSION', default='oh-pages') +OSMCHA_FRONTEND_VERSION = env("OSMCHA_FRONTEND_VERSION", default="oh-pages") # MapRoulette API CONFIG -MAP_ROULETTE_API_KEY = env('MAP_ROULETTE_API_KEY', default=None) -MAP_ROULETTE_API_URL = env('MAP_ROULETTE_API_URL', default="https://maproulette.org/api/v2/") +MAP_ROULETTE_API_KEY = env("MAP_ROULETTE_API_KEY", default=None) +MAP_ROULETTE_API_URL = env( + "MAP_ROULETTE_API_URL", default="https://maproulette.org/api/v2/" +) # Define the URL to where the user will be redirected after the authentication # in OSM website OAUTH_REDIRECT_URI = env( - 'OAUTH_REDIRECT_URI', - default='http://localhost:8000/oauth-landing.html' - ) + "OAUTH_REDIRECT_URI", default="http://localhost:8000/oauth-landing.html" +) + +# Default primary key field type +# https://docs.djangoproject.com/en/dev/ref/settings/#default-auto-field + +DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField" diff --git a/manage.py b/manage.py index 7b367ffe..714c549f 100755 --- a/manage.py +++ b/manage.py @@ -1,10 +1,20 @@ -#!/usr/bin/env python import os import sys -if __name__ == "__main__": - os.environ.setdefault("DJANGO_SETTINGS_MODULE", "config.settings.local") - from django.core.management import execute_from_command_line +def main(): + os.environ.setdefault("DJANGO_SETTINGS_MODULE", "config.settings.local") + try: + from django.core.management import execute_from_command_line + except ImportError as exc: + raise ImportError( + "Couldn't import Django. Are you sure it's installed and " + "available on your PYTHONPATH environment variable? Did you " + "forget to activate a virtual environment?" + ) from exc execute_from_command_line(sys.argv) + + +if __name__ == "__main__": + main() diff --git a/osmchadjango/changeset/apps.py b/osmchadjango/changeset/apps.py new file mode 100644 index 00000000..7fdc0e42 --- /dev/null +++ b/osmchadjango/changeset/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class ChangeSetConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'osmchadjango.changeset' diff --git a/osmchadjango/feature/apps.py b/osmchadjango/feature/apps.py new file mode 100644 index 00000000..cba45383 --- /dev/null +++ b/osmchadjango/feature/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class FeatureConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'osmchadjango.feature' diff --git a/osmchadjango/frontend/apps.py b/osmchadjango/frontend/apps.py index 5afe2968..4a817c7e 100644 --- a/osmchadjango/frontend/apps.py +++ b/osmchadjango/frontend/apps.py @@ -4,4 +4,5 @@ class FrontendConfig(AppConfig): - name = 'frontend' + default_auto_field = "django.db.models.BigAutoField" + name = "osmchadjango.frontend" diff --git a/osmchadjango/roulette_integration/apps.py b/osmchadjango/roulette_integration/apps.py index fa97552c..fd49b252 100644 --- a/osmchadjango/roulette_integration/apps.py +++ b/osmchadjango/roulette_integration/apps.py @@ -2,4 +2,6 @@ class RouletteIntegrationConfig(AppConfig): - name = 'roulette_integration' + name = "osmchadjango.roulette_integration" + default_auto_field = "django.db.models.BigAutoField" + diff --git a/osmchadjango/supervise/apps.py b/osmchadjango/supervise/apps.py index 23f7df2a..62da04fd 100644 --- a/osmchadjango/supervise/apps.py +++ b/osmchadjango/supervise/apps.py @@ -2,4 +2,6 @@ class SuperviseConfig(AppConfig): - name = 'supervise' + name = "osmchadjango.supervise" + default_auto_field = "django.db.models.BigAutoField" + diff --git a/osmchadjango/users/apps.py b/osmchadjango/users/apps.py new file mode 100644 index 00000000..12f6dbd7 --- /dev/null +++ b/osmchadjango/users/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class UsersConfig(AppConfig): + name = "osmchadjango.users" + default_auto_field = "django.db.models.BigAutoField" From 7be33721f7b7d812edbb8ba40bb62ef2ef5c8046 Mon Sep 17 00:00:00 2001 From: yunica Date: Tue, 13 Dec 2022 18:59:34 -0500 Subject: [PATCH 3/3] fix format and imports --- config/settings/common.py | 2 +- config/settings/production.py | 4 +- config/urls.py | 84 +-- osmchadjango/changeset/admin.py | 24 +- osmchadjango/changeset/apps.py | 4 +- osmchadjango/changeset/filters.py | 452 ++++++------ .../management/commands/delete_old_data.py | 19 +- .../management/commands/fetchchangesets.py | 2 +- .../management/commands/import_file.py | 12 +- .../commands/mark_harmful_changeset.py | 20 +- .../management/commands/merge_reasons.py | 36 +- .../management/commands/migrate_features.py | 119 ++-- osmchadjango/changeset/models.py | 62 +- osmchadjango/changeset/serializers.py | 121 ++-- osmchadjango/changeset/tasks.py | 68 +- osmchadjango/changeset/throttling.py | 7 +- osmchadjango/changeset/urls.py | 178 ++--- osmchadjango/changeset/views.py | 656 +++++++++--------- osmchadjango/feature/apps.py | 4 +- osmchadjango/feature/filters.py | 213 +++--- osmchadjango/feature/models.py | 45 +- osmchadjango/feature/serializers.py | 49 +- osmchadjango/feature/urls.py | 55 +- osmchadjango/feature/views.py | 338 +++++---- osmchadjango/frontend/apps.py | 2 - .../management/commands/update_frontend.py | 42 +- osmchadjango/frontend/models.py | 2 - osmchadjango/frontend/urls.py | 58 +- osmchadjango/frontend/views.py | 11 +- osmchadjango/roulette_integration/models.py | 12 +- .../roulette_integration/serializers.py | 9 +- osmchadjango/roulette_integration/urls.py | 26 +- osmchadjango/roulette_integration/utils.py | 45 +- osmchadjango/roulette_integration/views.py | 6 +- osmchadjango/supervise/apps.py | 1 - osmchadjango/supervise/models.py | 29 +- osmchadjango/supervise/serializers.py | 48 +- osmchadjango/supervise/urls.py | 50 +- osmchadjango/supervise/views.py | 125 ++-- osmchadjango/users/admin.py | 5 +- .../users/management/commands/clear_tokens.py | 2 +- .../management/commands/update_user_names.py | 9 +- osmchadjango/users/models.py | 30 +- osmchadjango/users/serializers.py | 47 +- osmchadjango/users/urls.py | 61 +- osmchadjango/users/utils.py | 23 +- osmchadjango/users/views.py | 171 ++--- 47 files changed, 1628 insertions(+), 1760 deletions(-) diff --git a/config/settings/common.py b/config/settings/common.py index c0cc64fa..a55cd3c6 100644 --- a/config/settings/common.py +++ b/config/settings/common.py @@ -25,7 +25,7 @@ # that is to say variables from the .env files will only be used if not defined # as environment variables. env_file = str(ROOT_DIR.path(".env")) - print("Loading : {}".format(env_file)) + print(f"Loading : {env_file}") env.read_env(env_file) print("The .env file has been loaded. See common.py for more information") diff --git a/config/settings/production.py b/config/settings/production.py index a49977b6..08953834 100644 --- a/config/settings/production.py +++ b/config/settings/production.py @@ -4,8 +4,6 @@ - Use djangosecure - Use MEMCACHE on Heroku ''' -from __future__ import absolute_import, unicode_literals - from .common import * # noqa @@ -78,7 +76,7 @@ # Configured to use Redis, if you prefer another method, comment the REDIS and # set up your prefered way. -REDIS_LOCATION = '{0}/{1}'.format(env('REDIS_URL', default='redis://127.0.0.1:6379'), 0) +REDIS_LOCATION = f"{env('REDIS_URL', default='redis://127.0.0.1:6379')}/{0}" CACHES = { 'default': { 'BACKEND': 'django_redis.cache.RedisCache', diff --git a/config/urls.py b/config/urls.py index 765d955e..d5b306c2 100644 --- a/config/urls.py +++ b/config/urls.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - from django.conf import settings from django.urls import include, path, re_path from django.conf.urls.static import static @@ -13,7 +10,7 @@ from drf_yasg import openapi -API_BASE_URL = 'api/v1/' +API_BASE_URL = "api/v1/" urlpatterns = [] @@ -21,78 +18,69 @@ if settings.DEBUG is False: # if DEBUG is True it will be served automatically urlpatterns += [ path( - 'static/', - static_views.serve, - {'document_root': settings.STATIC_ROOT} - ), - ] + "static/", static_views.serve, {"document_root": settings.STATIC_ROOT} + ), + ] api_urls = [ path( - '{}'.format(API_BASE_URL), - include("osmchadjango.changeset.urls", namespace="changeset") - ), + f"{API_BASE_URL}", + include("osmchadjango.changeset.urls", namespace="changeset"), + ), path( - '{}'.format(API_BASE_URL), - include("osmchadjango.supervise.urls", namespace="supervise") - ), + f"{API_BASE_URL}", + include("osmchadjango.supervise.urls", namespace="supervise"), + ), path( - '{}'.format(API_BASE_URL), - include("osmchadjango.users.urls", namespace="users") - ), + f"{API_BASE_URL}", include("osmchadjango.users.urls", namespace="users") + ), path( - '{}'.format(API_BASE_URL), - include("osmchadjango.roulette_integration.urls", namespace="challenge") - ), - ] + f"{API_BASE_URL}", + include("osmchadjango.roulette_integration.urls", namespace="challenge"), + ), +] schema_view = get_schema_view( openapi.Info( title="OSMCha API", - default_version='v1', + default_version="v1", description="OSMCha API Documentation", contact=openapi.Contact(email="osmcha-dev@openstreetmap.org"), license=openapi.License(name="Data © OpenStreetMap Contributors, ODBL license"), ), public=True, - permission_classes=(permissions.AllowAny,), + permission_classes=[permissions.AllowAny,], ) urlpatterns += [ # Django Admin - path('admin/', admin.site.urls), - + path("admin/", admin.site.urls), # api docs re_path( - r'^swagger(?P\.json|\.yaml)$', + r"^swagger(?P\.json|\.yaml)$", schema_view.without_ui(cache_timeout=0), - name='schema-json' - ), + name="schema-json", + ), re_path( - r'^api-docs/$', - schema_view.with_ui('swagger', cache_timeout=0), - name='schema-swagger-ui' - ), - + r"^api-docs/$", + schema_view.with_ui("swagger", cache_timeout=0), + name="schema-swagger-ui", + ), # include api_urls - path( - '', - include(api_urls) - ), - + path("", include(api_urls)), # frontend urls - path('', include("osmchadjango.frontend.urls", namespace="frontend")), - - ] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) + path("", include("osmchadjango.frontend.urls", namespace="frontend")), +] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) if settings.DEBUG: # This allows the error pages to be debugged during development, just visit # these url in browser to see how these error pages look like. import debug_toolbar + urlpatterns += [ - path('400/', defaults.bad_request), - path('403/', defaults.permission_denied), - path('404/', defaults.page_not_found), - path('500/', defaults.server_error), - path('__debug__/', include(debug_toolbar.urls)), - ] + path("400/", defaults.bad_request), + path("403/", defaults.permission_denied), + path("404/", defaults.page_not_found), + path("500/", defaults.server_error), + path("__debug__/", include(debug_toolbar.urls)), + ] diff --git a/osmchadjango/changeset/admin.py b/osmchadjango/changeset/admin.py index 90df78ba..18d99ca2 100644 --- a/osmchadjango/changeset/admin.py +++ b/osmchadjango/changeset/admin.py @@ -4,17 +4,25 @@ class ChangesetAdmin(admin.OSMGeoAdmin): - search_fields = ['id'] - list_display = ['id', 'user', 'create', 'modify', 'delete', 'checked', - 'date', 'check_user'] - list_filter = ['checked', 'is_suspect', 'reasons'] - date_hierarchy = 'date' + search_fields = ["id"] + list_display = [ + "id", + "user", + "create", + "modify", + "delete", + "checked", + "date", + "check_user", + ] + list_filter = ["checked", "is_suspect", "reasons"] + date_hierarchy = "date" class ReasonsAdmin(admin.ModelAdmin): - search_fields = ['name'] - list_display = ['name', 'is_visible', 'for_changeset', 'for_feature'] - list_filter = ['is_visible', 'for_changeset', 'for_feature'] + search_fields = ["name"] + list_display = ["name", "is_visible", "for_changeset", "for_feature"] + list_filter = ["is_visible", "for_changeset", "for_feature"] admin.site.register(Changeset, ChangesetAdmin) diff --git a/osmchadjango/changeset/apps.py b/osmchadjango/changeset/apps.py index 7fdc0e42..ec3607eb 100644 --- a/osmchadjango/changeset/apps.py +++ b/osmchadjango/changeset/apps.py @@ -2,5 +2,5 @@ class ChangeSetConfig(AppConfig): - default_auto_field = 'django.db.models.BigAutoField' - name = 'osmchadjango.changeset' + default_auto_field = "django.db.models.BigAutoField" + name = "osmchadjango.changeset" diff --git a/osmchadjango/changeset/filters.py b/osmchadjango/changeset/filters.py index b63f230c..7996b722 100644 --- a/osmchadjango/changeset/filters.py +++ b/osmchadjango/changeset/filters.py @@ -11,7 +11,7 @@ from django_filters.widgets import BooleanWidget from .models import Changeset -from ..users.models import MappingTeam +from osmchadjango.users.models import MappingTeam class ChangesetFilter(GeoFilterSet): @@ -20,263 +20,264 @@ class ChangesetFilter(GeoFilterSet): by the exact match (filter changesets that have all the search reasons) or by contains match (filter changesets that have any of the reasons). """ + geometry = GeometryFilter( - field_name='bbox', - lookup_expr='intersects', + field_name="bbox", + lookup_expr="intersects", help_text="""Geospatial filter of changeset whose bbox intersects with - another geometry. You can use any geometry type in this filter.""" - ) + another geometry. You can use any geometry type in this filter.""", + ) checked_by = filters.CharFilter( - field_name='check_user', - method='filter_checked_by', + field_name="check_user", + method="filter_checked_by", help_text="""Filter changesets that were checked by a user. Use commas - to search for more than one user.""" - ) + to search for more than one user.""", + ) users = filters.CharFilter( - field_name='user', - method='filter_users', + field_name="user", + method="filter_users", help_text="""Filter changesets created by a user. Use commas to search - for more than one user.""" - ) + for more than one user.""", + ) ids = filters.CharFilter( - field_name='id', - method='filter_ids', + field_name="id", + method="filter_ids", help_text="""Filter changesets by its ID. Use commas to search for more - than one id.""" - ) + than one id.""", + ) uids = filters.CharFilter( - field_name='uid', - method='filter_uids', + field_name="uid", + method="filter_uids", help_text="""Filter changesets by its uid. The uid is a unique identifier - of each user in OSM. Use commas to search for more than one uid.""" - ) + of each user in OSM. Use commas to search for more than one uid.""", + ) checked = filters.BooleanFilter( - field_name='checked', + field_name="checked", widget=BooleanWidget(), help_text="""Filter changesets that were checked or not. Use true/false, - 1/0 values.""" - ) + 1/0 values.""", + ) harmful = filters.BooleanFilter( - field_name='harmful', + field_name="harmful", widget=BooleanWidget(), help_text="""Filter changesets that were marked as harmful or not. - Use true/false, 1/0 values.""" - ) + Use true/false, 1/0 values.""", + ) is_suspect = filters.BooleanFilter( - field_name='is_suspect', + field_name="is_suspect", widget=BooleanWidget(), - help_text='Filter changesets that were considered suspect by OSMCHA.' - ) + help_text="Filter changesets that were considered suspect by OSMCHA.", + ) powerfull_editor = filters.BooleanFilter( - field_name='powerfull_editor', + field_name="powerfull_editor", widget=BooleanWidget(), help_text="""Filter changesets that were created using a software editor considered powerfull (those that allow to create, modify or delete - data in a batch).""" - ) + data in a batch).""", + ) order_by = filters.CharFilter( - field_name='order', - method='order_queryset', + field_name="order", + method="order_queryset", help_text="""Order the Changesets by one of the following fields: id, date, check_date, create, modify, delete or number_reasons. Use a minus sign (-) before the field name to reverse the ordering. - Default ordering is '-id'.""" - ) + Default ordering is '-id'.""", + ) hide_whitelist = filters.BooleanFilter( - field_name='user', - method='filter_whitelist', + field_name="user", + method="filter_whitelist", widget=BooleanWidget(), help_text="""If True, it will exclude the changesets created by the - users that you whitelisted.""" - ) + users that you whitelisted.""", + ) blacklist = filters.BooleanFilter( - field_name='user', - method='filter_blacklist', + field_name="user", + method="filter_blacklist", widget=BooleanWidget(), help_text="""If True, it will get only the changesets created by the - users that you blacklisted.""" - ) + users that you blacklisted.""", + ) mapping_teams = filters.CharFilter( - field_name='user', - method='filter_mapping_team', + field_name="user", + method="filter_mapping_team", help_text="""Filter changesets created by users that are on a Mapping - Team. It accepts a list of teams separated by commas.""" - ) + Team. It accepts a list of teams separated by commas.""", + ) exclude_teams = filters.CharFilter( - field_name='user', - method='exclude_mapping_team', + field_name="user", + method="exclude_mapping_team", help_text="""Exclude changesets created by users that are on a Mapping - Team. It accepts a list of teams separated by commas.""" - ) + Team. It accepts a list of teams separated by commas.""", + ) exclude_trusted_teams = filters.BooleanFilter( - field_name='user', - method='filter_hide_trusted_teams', + field_name="user", + method="filter_hide_trusted_teams", widget=BooleanWidget(), help_text="""If True, it will exclude the changesets created by the - users that are part of trusted teams.""" - ) + users that are part of trusted teams.""", + ) area_lt = filters.CharFilter( - field_name='user', - method='filter_area_lt', + field_name="user", + method="filter_area_lt", help_text="""Filter changesets that have a bbox area lower than X times the area of your geospatial filter. For example, if the bbox or geometry you defined in your filter has an area of 1 degree and you set 'area_lt=2', it will filter the changesets whose bbox area is - lower than 2 degrees.""" - ) + lower than 2 degrees.""", + ) create__gte = filters.NumberFilter( - field_name='create', - lookup_expr='gte', + field_name="create", + lookup_expr="gte", help_text="""Filter changesets whose number of elements created are - greater than or equal to a number.""" - ) + greater than or equal to a number.""", + ) create__lte = filters.NumberFilter( - field_name='create', - lookup_expr='lte', + field_name="create", + lookup_expr="lte", help_text="""Filter changesets whose number of elements created are - lower than or equal to a number.""" - ) + lower than or equal to a number.""", + ) modify__gte = filters.NumberFilter( - field_name='modify', - lookup_expr='gte', + field_name="modify", + lookup_expr="gte", help_text="""Filter changesets whose number of elements modified are - greater than or equal to a number.""" - ) + greater than or equal to a number.""", + ) modify__lte = filters.NumberFilter( - field_name='modify', - lookup_expr='lte', + field_name="modify", + lookup_expr="lte", help_text="""Filter changesets whose number of elements modified are - lower than or equal to a number.""" - ) + lower than or equal to a number.""", + ) delete__gte = filters.NumberFilter( - field_name='delete', - lookup_expr='gte', + field_name="delete", + lookup_expr="gte", help_text="""Filter changesets whose number of elements deleted are - greater than or equal to a number.""" - ) + greater than or equal to a number.""", + ) delete__lte = filters.NumberFilter( - field_name='delete', - lookup_expr='lte', + field_name="delete", + lookup_expr="lte", help_text="""Filter changesets whose number of elements deleted are - lower than or equal to a number.""" - ) + lower than or equal to a number.""", + ) comments_count__gte = filters.NumberFilter( - field_name='comments_count', - lookup_expr='gte', + field_name="comments_count", + lookup_expr="gte", help_text="""Filter changesets whose number of comments are greater than - or equal to a number.""" - ) + or equal to a number.""", + ) comments_count__lte = filters.NumberFilter( - field_name='comments_count', - lookup_expr='lte', + field_name="comments_count", + lookup_expr="lte", help_text="""Filter changesets whose number of comments are lower than - or equal to a number.""" - ) + or equal to a number.""", + ) last_days = filters.NumberFilter( - field_name='date', - method='get_past_n_days', - help_text="Filter changesets whose date is not older than a determined number of days" - ) + field_name="date", + method="get_past_n_days", + help_text="Filter changesets whose date is not older than a determined number of days", + ) date__gte = filters.DateTimeFilter( - field_name='date', - lookup_expr='gte', + field_name="date", + lookup_expr="gte", help_text="""Filter changesets whose date is greater than or equal to a - date or a datetime value.""" - ) + date or a datetime value.""", + ) date__lte = filters.DateTimeFilter( - field_name='date', - lookup_expr='lte', + field_name="date", + lookup_expr="lte", help_text="""Filter changesets whose date is lower than or equal to a - date or a datetime value.""" - ) + date or a datetime value.""", + ) check_date__gte = filters.DateTimeFilter( - field_name='check_date', - lookup_expr='gte', + field_name="check_date", + lookup_expr="gte", help_text="""Filter changesets whose check_date is greater than or equal - to a date or a datetime value.""" - ) + to a date or a datetime value.""", + ) check_date__lte = filters.DateTimeFilter( - field_name='check_date', - lookup_expr='lte', + field_name="check_date", + lookup_expr="lte", help_text="""Filter changesets whose check_date is lower than or equal - to a date or a datetime value.""" - ) + to a date or a datetime value.""", + ) editor = filters.CharFilter( - field_name='editor', - lookup_expr='icontains', + field_name="editor", + lookup_expr="icontains", help_text="""Filter changesets created with a software editor. It uses the icontains lookup expression, so a query for 'josm' will return - changesets created or last modified with all JOSM versions.""" - ) + changesets created or last modified with all JOSM versions.""", + ) comment = filters.CharFilter( - field_name='comment', - lookup_expr='icontains', + field_name="comment", + lookup_expr="icontains", help_text="""Filter changesets by its comment field using the icontains - lookup expression.""" - ) + lookup expression.""", + ) source = filters.CharFilter( - field_name='source', - lookup_expr='icontains', + field_name="source", + lookup_expr="icontains", help_text="""Filter changesets by its source field using the icontains - lookup expression.""" - ) + lookup expression.""", + ) imagery_used = filters.CharFilter( - field_name='imagery_used', - lookup_expr='icontains', + field_name="imagery_used", + lookup_expr="icontains", help_text="""Filter changesets by its imagery_used field using the - icontains lookup expression.""" - ) + icontains lookup expression.""", + ) reasons = filters.CharFilter( - field_name='reasons', - method='filter_any_reasons', + field_name="reasons", + method="filter_any_reasons", help_text="""Filter changesets that have one or more of the Suspicion - Reasons. Inform the Suspicion Reasons ids separated by commas.""" - ) + Reasons. Inform the Suspicion Reasons ids separated by commas.""", + ) all_reasons = filters.CharFilter( - field_name='reasons', - method='filter_all_reasons', + field_name="reasons", + method="filter_all_reasons", help_text="""Filter changesets that have ALL the Suspicion Reasons of a - list. Inform the Suspicion Reasons ids separated by commas.""" - ) + list. Inform the Suspicion Reasons ids separated by commas.""", + ) number_reasons__gte = filters.NumberFilter( - field_name='number_reasons', - method='filter_number_reasons', + field_name="number_reasons", + method="filter_number_reasons", help_text="""Filter changesets whose number of Suspicion Reasons is - equal or greater than a value.""" - ) + equal or greater than a value.""", + ) tags = filters.CharFilter( - field_name='tags', - method='filter_any_reasons', + field_name="tags", + method="filter_any_reasons", help_text="""Filter changesets that have one or more of the Tags. Inform - the Tags ids separated by commas.""" - ) + the Tags ids separated by commas.""", + ) all_tags = filters.CharFilter( - field_name='tags', - method='filter_all_reasons', + field_name="tags", + method="filter_all_reasons", help_text="""Filter changesets that have ALL the Tags of a list. Inform - the Tags ids separated by commas.""" - ) + the Tags ids separated by commas.""", + ) metadata = filters.CharFilter( - field_name='metadata', - method='filter_metadata', - help_text="""Filter changesets by the metadata fields.""" - ) + field_name="metadata", + method="filter_metadata", + help_text="""Filter changesets by the metadata fields.""", + ) tag_changes = filters.CharFilter( - field_name='tag_changes', - method='filter_tag_changes', + field_name="tag_changes", + method="filter_tag_changes", help_text="""Filter changesets by the tag_changes field. It supports a combination of OSM tags and will query using both the old and new values. To query by any value use key=*. - """ - ) + """, + ) all_tag_changes = filters.CharFilter( - field_name='tag_changes', - method='filter_all_tag_changes', + field_name="tag_changes", + method="filter_all_tag_changes", help_text="""Filter changesets by the tag_changes field using the AND logic. It supports a combination of OSM tags and will query using both the old and new values. To query by any value use key=*. - """ - ) + """, + ) def get_past_n_days(self, queryset, field_name, value): start_date = date.today() - timedelta(days=int(value)) @@ -284,19 +285,19 @@ def get_past_n_days(self, queryset, field_name, value): def split_values(self, value): return [ - [i.strip() for i in t.split('=')] # remove leading and ending spaces - for t in value.split(',') - if len(t.split('=')) == 2 - ] + [i.strip() for i in t.split("=")] # remove leading and ending spaces + for t in value.split(",") + if len(t.split("=")) == 2 + ] def filter_all_tag_changes(self, queryset, name, value): for query in self.split_values(value): - if query[1] == '*': - key = 'tag_changes__has_key' + if query[1] == "*": + key = "tag_changes__has_key" value = query[0] queryset = queryset.filter(**{key: value}) else: - key = 'tag_changes__{}__contains'.format(query[0]) + key = f"tag_changes__{query[0]}__contains" queryset = queryset.filter(**{key: query[1]}) return queryset @@ -304,34 +305,32 @@ def filter_tag_changes(self, queryset, name, value): or_condition = Q() keys = [] for query in self.split_values(value): - if query[1] == '*': + if query[1] == "*": keys.append(query[0]) else: - key = 'tag_changes__{}__contains'.format(query[0]) + key = f"tag_changes__{query[0]}__contains" or_condition.add(Q(**{key: query[1]}), Q.OR) if len(keys): - or_condition.add(Q(**{'tag_changes__has_any_keys': keys}), Q.OR) + or_condition.add(Q(**{"tag_changes__has_any_keys": keys}), Q.OR) queryset = queryset.filter(or_condition) return queryset def filter_metadata(self, queryset, name, value): for query in self.split_values(value): - if '__' in query[0]: + if "__" in query[0]: # handle both int values and other lookup options like __exact or __contains - key = 'metadata__{}'.format( - query[0].replace('__min', '__gte').replace('__max', '__lte') - ) + key = f"metadata__{query[0].replace('__min', '__gte').replace('__max', '__lte')}" try: value = int(query[1]) except ValueError: value = query[1] - elif query[1] == '*': - key = 'metadata__has_key' + elif query[1] == "*": + key = "metadata__has_key" value = query[0] else: # default option is to use the icontains condition - key = 'metadata__{}__icontains'.format(query[0]) + key = f"metadata__{query[0]}__icontains" value = query[1] queryset = queryset.filter(**{key: value}) return queryset @@ -339,9 +338,8 @@ def filter_metadata(self, queryset, name, value): def filter_whitelist(self, queryset, name, value): if value: whitelist = self.request.user.whitelists.values_list( - 'whitelist_user', - flat=True - ) + "whitelist_user", flat=True + ) return queryset.exclude(user__in=whitelist) else: return queryset @@ -349,29 +347,28 @@ def filter_whitelist(self, queryset, name, value): def filter_blacklist(self, queryset, name, value): if value: blacklist = self.request.user.blacklisteduser_set.values_list( - 'uid', - flat=True - ) + "uid", flat=True + ) return queryset.filter(uid__in=blacklist) else: return queryset def get_username_from_teams(self, teams): users = [] - for i in teams.values_list('users', flat=True): + for i in teams.values_list("users", flat=True): values = i if type(values) in [str, bytes, bytearray]: values = json.loads(values) for e in values: - users.append(e.get('username')) + users.append(e.get("username")) return users def filter_mapping_team(self, queryset, name, value): try: # added `if team` to avoid empty strings teams = MappingTeam.objects.filter( - name__in=[team.strip() for team in value.split(',') if team] - ) + name__in=[team.strip() for team in value.split(",") if team] + ) users = self.get_username_from_teams(teams) return queryset.filter(user__in=users) except MappingTeam.DoesNotExist: @@ -380,8 +377,8 @@ def filter_mapping_team(self, queryset, name, value): def exclude_mapping_team(self, queryset, name, value): try: teams = MappingTeam.objects.filter( - name__in=[team.strip() for team in value.split(',') if team] - ) + name__in=[team.strip() for team in value.split(",") if team] + ) users = self.get_username_from_teams(teams) return queryset.exclude(user__in=users) except MappingTeam.DoesNotExist: @@ -397,60 +394,71 @@ def filter_hide_trusted_teams(self, queryset, name, value): def filter_checked_by(self, queryset, name, value): if (self.request is None or self.request.user.is_authenticated) and value: - lookup = '__'.join([name, 'name__in']) - users = [t.strip() for t in value.split(',')] + lookup = "__".join([name, "name__in"]) + users = [t.strip() for t in value.split(",")] return queryset.filter(**{lookup: users}) else: return queryset def filter_users(self, queryset, name, value): if (self.request is None or self.request.user.is_authenticated) and value: - lookup = '__'.join([name, 'in']) - users_array = [t.strip() for t in value.split(',')] + lookup = "__".join([name, "in"]) + users_array = [t.strip() for t in value.split(",")] return queryset.filter(**{lookup: users_array}) else: return queryset def filter_ids(self, queryset, name, value): - lookup = '__'.join([name, 'in']) - values = [int(n) for n in value.split(',')] + lookup = "__".join([name, "in"]) + values = [int(n) for n in value.split(",")] return queryset.filter(**{lookup: values}) def filter_uids(self, queryset, name, value): if (self.request is None or self.request.user.is_authenticated) and value: - lookup = '__'.join([name, 'in']) - values = [n for n in value.split(',')] + lookup = "__".join([name, "in"]) + values = [n for n in value.split(",")] return queryset.filter(**{lookup: values}) else: return queryset def filter_any_reasons(self, queryset, name, value): - lookup = '__'.join([name, 'id', 'in']) - values = [int(t) for t in value.split(',')] + lookup = "__".join([name, "id", "in"]) + values = [int(t) for t in value.split(",")] return queryset.filter(**{lookup: values}).distinct() def filter_all_reasons(self, queryset, name, value): - lookup = '__'.join([name, 'id']) - values = [int(t) for t in value.split(',')] + lookup = "__".join([name, "id"]) + values = [int(t) for t in value.split(",")] for term in values: queryset = queryset.filter(**{lookup: term}) return queryset def filter_number_reasons(self, queryset, name, value): - lookup = '__'.join([name, 'gte']) - queryset = queryset.annotate(number_reasons=Count('reasons')) + lookup = "__".join([name, "gte"]) + queryset = queryset.annotate(number_reasons=Count("reasons")) return queryset.filter(**{lookup: value}) def order_queryset(self, queryset, name, value): allowed_fields = [ - 'date', '-date', 'id', 'check_date', '-check_date', 'create', - 'modify', 'delete', '-create', '-modify', '-delete', - 'number_reasons', '-number_reasons', 'comments_count', - '-comments_count' - ] + "date", + "-date", + "id", + "check_date", + "-check_date", + "create", + "modify", + "delete", + "-create", + "-modify", + "-delete", + "number_reasons", + "-number_reasons", + "comments_count", + "-comments_count", + ] if value in allowed_fields: - if value in ['number_reasons', '-number_reasons']: - queryset = queryset.annotate(number_reasons=Count('reasons')) + if value in ["number_reasons", "-number_reasons"]: + queryset = queryset.annotate(number_reasons=Count("reasons")) return queryset.order_by(value) else: return queryset @@ -461,20 +469,20 @@ def filter_area_lt(self, queryset, name, value): changesets that are greater than 5 times the filter area, you need to set the value to 5. """ - if 'geometry' in self.data.keys(): + if "geometry" in self.data.keys(): try: - filter_area = self.data['geometry'].area + filter_area = self.data["geometry"].area except AttributeError: - filter_area = GeometryField().to_internal_value( - self.data['geometry'] - ).area - return queryset.filter(area__lt=float(value)*filter_area) - elif 'in_bbox' in self.data.keys(): + filter_area = ( + GeometryField().to_internal_value(self.data["geometry"]).area + ) + return queryset.filter(area__lt=float(value) * filter_area) + elif "in_bbox" in self.data.keys(): try: filter_area = Polygon.from_bbox( - (float(n) for n in self.data['in_bbox'].split(',')) - ).area - return queryset.filter(area__lt=float(value)*filter_area) + (float(n) for n in self.data["in_bbox"].split(",")) + ).area + return queryset.filter(area__lt=float(value) * filter_area) except ValueError: return queryset else: @@ -482,4 +490,4 @@ def filter_area_lt(self, queryset, name, value): class Meta: model = Changeset - fields = ['geometry', 'users', 'area_lt'] + fields = ["geometry", "users", "area_lt"] diff --git a/osmchadjango/changeset/management/commands/delete_old_data.py b/osmchadjango/changeset/management/commands/delete_old_data.py index 5af6b792..b0f52b99 100644 --- a/osmchadjango/changeset/management/commands/delete_old_data.py +++ b/osmchadjango/changeset/management/commands/delete_old_data.py @@ -1,8 +1,8 @@ from django.core.management.base import BaseCommand from django.utils import timezone -from ...models import Changeset -from ....feature.models import Feature +from osmchadjango.changeset.models import Changeset +from osmchadjango.feature.models import Feature def get_six_months_ago(): @@ -10,18 +10,11 @@ def get_six_months_ago(): class Command(BaseCommand): - help = """Command to delete all the unchecked changesets older than 180 days. - """ + help = """Command to delete all the unchecked changesets older than 180 days.""" def handle(self, *args, **options): date = get_six_months_ago Feature.objects.filter( - changeset__date__lt=date(), - changeset__checked=False, - checked=False - ).delete() - Changeset.objects.filter( - date__lt=date(), - checked=False, - features=None - ).delete() + changeset__date__lt=date(), changeset__checked=False, checked=False + ).delete() + Changeset.objects.filter(date__lt=date(), checked=False, features=None).delete() diff --git a/osmchadjango/changeset/management/commands/fetchchangesets.py b/osmchadjango/changeset/management/commands/fetchchangesets.py index add05e74..ac822887 100644 --- a/osmchadjango/changeset/management/commands/fetchchangesets.py +++ b/osmchadjango/changeset/management/commands/fetchchangesets.py @@ -1,6 +1,6 @@ from django.core.management.base import BaseCommand -from ...tasks import fetch_latest +from osmchadjango.changeset.tasks import fetch_latest class Command(BaseCommand): diff --git a/osmchadjango/changeset/management/commands/import_file.py b/osmchadjango/changeset/management/commands/import_file.py index aa1c3d8c..9cf5d160 100644 --- a/osmchadjango/changeset/management/commands/import_file.py +++ b/osmchadjango/changeset/management/commands/import_file.py @@ -3,20 +3,18 @@ from osmcha.changeset import ChangesetList -from ...tasks import create_changeset +from osmchadjango.changeset.tasks import create_changeset class Command(BaseCommand): help = """Read a local file and import all changesets.""" def add_arguments(self, parser): - parser.add_argument('filename', nargs=1, type=str) + parser.add_argument("filename", nargs=1, type=str) def handle(self, *args, **options): - filename = options['filename'][0] + filename = options["filename"][0] cl = ChangesetList(filename, geojson=settings.CHANGESETS_FILTER) - imported = [create_changeset(c['id']) for c in cl.changesets] + imported = [create_changeset(c["id"]) for c in cl.changesets] - self.stdout.write( - '{} changesets created from {}.'.format(len(imported), filename) - ) + self.stdout.write(f"{len(imported)} changesets created from {filename}.") diff --git a/osmchadjango/changeset/management/commands/mark_harmful_changeset.py b/osmchadjango/changeset/management/commands/mark_harmful_changeset.py index cf4c6938..46c1109f 100644 --- a/osmchadjango/changeset/management/commands/mark_harmful_changeset.py +++ b/osmchadjango/changeset/management/commands/mark_harmful_changeset.py @@ -1,24 +1,24 @@ from django.core.management.base import BaseCommand from django.utils import timezone -from ...models import Changeset -from ....users.models import User +from osmchadjango.changeset.models import Changeset +from osmchadjango.users.models import User class Command(BaseCommand): - help = 'Marks a list of changesets as harmful' + help = "Marks a list of changesets as harmful" def add_arguments(self, parser): - parser.add_argument('check_username', nargs='+', type=str) - parser.add_argument('filename', nargs='+', type=str) + parser.add_argument("check_username", nargs="+", type=str) + parser.add_argument("filename", nargs="+", type=str) def handle(self, *args, **options): - check_username = options['check_username'][0] + check_username = options["check_username"][0] check_user = User.objects.filter(username=check_username) if check_user: check_user = check_user[0] - filename = options['filename'][0] - fr = open(filename, 'r') + filename = options["filename"][0] + fr = open(filename, "r") for line in fr: changeset_id = line.rstrip() try: @@ -28,6 +28,6 @@ def handle(self, *args, **options): changeset.check_user = check_user changeset.check_date = timezone.now() changeset.save() - except: - pass + except Exception as ex: + print(ex) fr.close() diff --git a/osmchadjango/changeset/management/commands/merge_reasons.py b/osmchadjango/changeset/management/commands/merge_reasons.py index b033514c..18cdaaf7 100644 --- a/osmchadjango/changeset/management/commands/merge_reasons.py +++ b/osmchadjango/changeset/management/commands/merge_reasons.py @@ -1,8 +1,7 @@ from django.core.management.base import BaseCommand -from django.conf import settings from django.db import connection -from ...models import SuspicionReasons +from osmchadjango.changeset.models import SuspicionReasons class Command(BaseCommand): @@ -12,21 +11,15 @@ class Command(BaseCommand): """ def add_arguments(self, parser): - parser.add_argument('reason_1', nargs=1, type=str) - parser.add_argument('reason_2', nargs=1, type=str) + parser.add_argument("reason_1", nargs=1, type=str) + parser.add_argument("reason_2", nargs=1, type=str) def handle(self, *args, **options): try: - origin_reason = SuspicionReasons.objects.get( - id=options['reason_1'][0] - ) - final_reason = SuspicionReasons.objects.get( - id=options['reason_2'][0] - ) + origin_reason = SuspicionReasons.objects.get(id=options["reason_1"][0]) + final_reason = SuspicionReasons.objects.get(id=options["reason_2"][0]) changesets = origin_reason.changesets.exclude(reasons=final_reason) - excluded_changesets = final_reason.changesets.filter( - reasons=final_reason - ) + excluded_changesets = final_reason.changesets.filter(reasons=final_reason) changeset_number = changesets.count() origin_reason_name = origin_reason.name with connection.cursor() as cursor: @@ -38,21 +31,18 @@ def handle(self, *args, **options): [ final_reason.id, origin_reason.id, - tuple([c.id for c in excluded_changesets]) - ] - ) + tuple([c.id for c in excluded_changesets]), + ], + ) origin_reason.delete() self.stdout.write( - """{} changesets were moved from '{}' to '{}' SuspicionReasons. - '{}' has been successfully deleted. - """.format( - changeset_number, origin_reason_name, final_reason.name, - origin_reason_name - ) + f"""{changeset_number} changesets were moved from '{origin_reason_name}' to '{final_reason.name}' SuspicionReasons. + '{origin_reason_name}' has been successfully deleted. + """ ) except SuspicionReasons.DoesNotExist: self.stdout.write( """Verify the SuspicionReasons ids. One or both of them does not exist. """ - ) + ) diff --git a/osmchadjango/changeset/management/commands/migrate_features.py b/osmchadjango/changeset/management/commands/migrate_features.py index 60d6f493..bf5d70c3 100644 --- a/osmchadjango/changeset/management/commands/migrate_features.py +++ b/osmchadjango/changeset/management/commands/migrate_features.py @@ -1,109 +1,114 @@ import json -from datetime import date, timedelta from django.core.management.base import BaseCommand from django.db import transaction -from django.utils import timezone -from ...models import Changeset +from osmchadjango.changeset.models import Changeset class Command(BaseCommand): - help = '''Convert the features of the changesets to the new JSONField + help = """Convert the features of the changesets to the new JSONField format. The changesets will be filtered according to the informed start and end dates. - ''' + """ def add_arguments(self, parser): - parser.add_argument('start_date', nargs='+', type=str) - parser.add_argument('end_date', nargs='+', type=str) + parser.add_argument("start_date", nargs="+", type=str) + parser.add_argument("end_date", nargs="+", type=str) def handle(self, *args, **options): - start_date = options['start_date'][0] - end_date = options['end_date'][0] + start_date = options["start_date"][0] + end_date = options["end_date"][0] migrate_features(start_date, end_date) def filtered_json(feature): PRIMARY_TAGS = [ - 'aerialway', - 'aeroway', - 'amenity', - 'barrier', - 'boundary', - 'building', - 'craft', - 'emergency', - 'geological', - 'highway', - 'historic', - 'landuse', - 'leisure', - 'man_made', - 'military', - 'natural', - 'office', - 'place', - 'power', - 'public_transport', - 'railway', - 'route', - 'shop', - 'tourism', - 'waterway' + "aerialway", + "aeroway", + "amenity", + "barrier", + "boundary", + "building", + "craft", + "emergency", + "geological", + "highway", + "historic", + "landuse", + "leisure", + "man_made", + "military", + "natural", + "office", + "place", + "power", + "public_transport", + "railway", + "route", + "shop", + "tourism", + "waterway", ] data = { "osm_id": feature.osm_id, - "url": "{}-{}".format(feature.osm_type, feature.osm_id), + "url": f"{feature.osm_type}-{feature.osm_id}", "version": feature.osm_version, - "reasons": [reason.id for reason in feature.reasons.all()] + "reasons": [reason.id for reason in feature.reasons.all()], } - try: - if 'properties' in json.loads(feature.geojson).keys(): - properties = json.loads(feature.geojson)['properties'] - if 'name' in properties.keys(): - data['name'] = properties['name'] + json_data = json.loads(feature.geojson) + if "properties" in json_data.keys(): + properties = json_data.get("properties") + if "name" in properties.keys(): + data["name"] = properties.get("name") [ - properties.pop(key) for key in list(properties.keys()) + properties.pop(key) + for key in list(properties.keys()) if key not in PRIMARY_TAGS ] - data['primary_tags'] = properties + data["primary_tags"] = properties except TypeError: - if 'properties' in feature.geojson.keys(): - properties = feature.geojson['properties'] - if 'name' in properties.keys(): - data['name'] = properties['name'] + if "properties" in feature.geojson.keys(): + properties = feature.geojson["properties"] + if "name" in properties.keys(): + data["name"] = properties["name"] [ - properties.pop(key) for key in list(properties.keys()) + properties.pop(key) + for key in list(properties.keys()) if key not in PRIMARY_TAGS ] - data['primary_tags'] = properties + data["primary_tags"] = properties return data def migrate_features(start_date, end_date): - last_changeset_date = Changeset.objects.order_by('-date')[0].date - changesets = Changeset.objects.filter( + # last_changeset_date = Changeset.objects.order_by('-date')[0].date + changesets = ( + Changeset.objects.filter( features__isnull=False, date__gte=start_date, date__lte=end_date, - new_features=[] - ).prefetch_related('features').order_by('id').distinct() + new_features=[], + ) + .prefetch_related("features") + .order_by("id") + .distinct() + ) changeset_count = changesets.count() migrated = 0 - print('{} changesets to migrate'.format(changesets.count())) + print(f"{changesets.count()} changesets to migrate") while migrated < changeset_count: with transaction.atomic(): for changeset in changesets[migrated : migrated + 1000]: new_features_data = [ filtered_json(feature) for feature in changeset.features.all() - ] + ] changeset.new_features = new_features_data - changeset.save(update_fields=['new_features']) - print('{}-{} changesets migrated'.format(migrated, migrated + 1000)) + changeset.save(update_fields=["new_features"]) + print(f"{migrated}-{(migrated + 1000)} changesets migrated") migrated += 1000 diff --git a/osmchadjango/changeset/models.py b/osmchadjango/changeset/models.py index be939621..06e3045d 100644 --- a/osmchadjango/changeset/models.py +++ b/osmchadjango/changeset/models.py @@ -1,9 +1,9 @@ from django.contrib.gis.db import models from django.contrib.postgres.indexes import GinIndex from django.contrib.postgres.fields import JSONField -from django.utils.translation import ugettext, ugettext_lazy as _ +from django.utils.translation import ugettext_lazy as _ -from ..users.models import User +from osmchadjango.users.models import User class SuspicionReasons(models.Model): @@ -21,9 +21,9 @@ def save(self, *args, **kwargs): super(SuspicionReasons, self).save(*args, **kwargs) class Meta: - ordering = ['id'] - verbose_name = 'Suspicion reason' - verbose_name_plural = 'Suspicion reasons' + ordering = ["id"] + verbose_name = "Suspicion reason" + verbose_name_plural = "Suspicion reasons" class Tag(models.Model): @@ -41,34 +41,35 @@ def save(self, *args, **kwargs): super(Tag, self).save(*args, **kwargs) class Meta: - ordering = ['id'] + ordering = ["id"] class UserWhitelist(models.Model): - user = models.ForeignKey( - User, related_name='whitelists', on_delete=models.CASCADE - ) + user = models.ForeignKey(User, related_name="whitelists", on_delete=models.CASCADE) whitelist_user = models.CharField(max_length=1000, db_index=True) def __str__(self): - return '{} whitelisted by {}'.format(self.whitelist_user, self.user) + return f"{self.whitelist_user} whitelisted by {self.user}" class Meta: - ordering = ['-whitelist_user'] - unique_together = ('user', 'whitelist_user',) + ordering = ["-whitelist_user"] + unique_together = ( + "user", + "whitelist_user", + ) class Changeset(models.Model): user = models.CharField(max_length=1000, db_index=True) - uid = models.CharField(_('User ID'), max_length=255, blank=True, null=True) + uid = models.CharField(_("User ID"), max_length=255, blank=True, null=True) editor = models.CharField(max_length=255, blank=True, null=True) - powerfull_editor = models.BooleanField(_('Powerfull Editor'), default=False) + powerfull_editor = models.BooleanField(_("Powerfull Editor"), default=False) comment = models.CharField(max_length=1000, blank=True, null=True) comments_count = models.IntegerField(null=True, default=0) source = models.CharField(max_length=1000, blank=True, null=True) imagery_used = models.CharField(max_length=1000, blank=True, null=True) date = models.DateTimeField(null=True, db_index=True) - reasons = models.ManyToManyField(SuspicionReasons, related_name='changesets') + reasons = models.ManyToManyField(SuspicionReasons, related_name="changesets") new_features = JSONField(default=list) reviewed_features = JSONField(default=list) tag_changes = JSONField(default=dict) @@ -79,16 +80,16 @@ class Changeset(models.Model): area = models.FloatField(blank=True, null=True) is_suspect = models.BooleanField() harmful = models.NullBooleanField() - tags = models.ManyToManyField(Tag, related_name='changesets') + tags = models.ManyToManyField(Tag, related_name="changesets") checked = models.BooleanField(default=False) check_user = models.ForeignKey( User, on_delete=models.SET_NULL, null=True, blank=True - ) + ) check_date = models.DateTimeField(null=True, blank=True) metadata = JSONField(default=dict) def __str__(self): - return '%s' % self.id + return str(self.id) def save(self, *args, **kwargs): # populate area field when saving object @@ -98,42 +99,39 @@ def save(self, *args, **kwargs): def osm_link(self): """Return the link to the changeset page on OSM website.""" - return 'https://www.openstreetmap.org/changeset/{}'.format(self.id) + return f"https://www.openstreetmap.org/changeset/{self.id}" def josm_link(self): """Return link to open changeset in JOSM.""" josm_base = "http://127.0.0.1:8111/import?url=" - changeset_url = "{}/{}/{}".format( - "https://www.openstreetmap.org/api/0.6/changeset", - self.id, - "download" - ) - return "{}{}".format(josm_base, changeset_url) + changeset_url = ( + f"https://www.openstreetmap.org/api/0.6/changeset/{self.id}/download" + ) + return f"{josm_base}{changeset_url}" def id_link(self): """Return link to open the area of the changeset in iD editor.""" id_base = "https://www.openstreetmap.org/edit?editor=id#map=16" if self.bbox: centroid = [round(c, 5) for c in self.bbox.centroid.coords] - return "{}/{}/{}".format(id_base, centroid[1], centroid[0]) + return f"{id_base}/{centroid[1]}/{centroid[0]}" else: return "" class Meta: - ordering = ['-id'] - indexes = [ - GinIndex(fields=['tag_changes']) - ] + ordering = ["-id"] + indexes = [GinIndex(fields=["tag_changes"])] class Import(models.Model): """Class to register the import of Changesets.""" + start = models.IntegerField() end = models.IntegerField() - date = models.DateTimeField(_('Date of the import'), auto_now_add=True) + date = models.DateTimeField(_("Date of the import"), auto_now_add=True) def __str__(self): - return '%s %i - %i' % (_('Import'), self.start, self.end) + return f"{_('Import')} {self.start} - {self.end}" def save(self, *args, **kwargs): self.full_clean() diff --git a/osmchadjango/changeset/serializers.py b/osmchadjango/changeset/serializers.py index 54ea46ee..d9b1fac6 100644 --- a/osmchadjango/changeset/serializers.py +++ b/osmchadjango/changeset/serializers.py @@ -1,8 +1,15 @@ +from abc import ABC + from rest_framework.fields import ReadOnlyField, SerializerMethodField, CharField from rest_framework.serializers import ( - ModelSerializer, ListSerializer, BaseSerializer, PrimaryKeyRelatedField, - Serializer, ChoiceField, IntegerField - ) + ModelSerializer, + ListSerializer, + BaseSerializer, + PrimaryKeyRelatedField, + Serializer, + ChoiceField, + IntegerField, +) from rest_framework_gis.serializers import GeoFeatureModelSerializer from .models import Changeset, Tag, SuspicionReasons, UserWhitelist @@ -11,40 +18,41 @@ class SuspicionReasonsSerializer(ModelSerializer): class Meta: model = SuspicionReasons - fields = '__all__' + fields = "__all__" class BasicSuspicionReasonsSerializer(ModelSerializer): class Meta: model = SuspicionReasons - fields = ('id', 'name') + fields = ("id", "name") class TagSerializer(ModelSerializer): class Meta: model = Tag - fields = '__all__' + fields = "__all__" class BasicTagSerializer(ModelSerializer): class Meta: model = Tag - fields = ('id', 'name') + fields = ("id", "name") class ChangesetSerializerToStaff(GeoFeatureModelSerializer): """Serializer with all the Changeset model fields, except the 'powerfull_editor'. """ - check_user = ReadOnlyField(source='check_user.name', default=None) + + check_user = ReadOnlyField(source="check_user.name", default=None) reasons = BasicSuspicionReasonsSerializer(many=True, read_only=True) tags = BasicTagSerializer(many=True, read_only=True) - features = ReadOnlyField(source='new_features', default=list) + features = ReadOnlyField(source="new_features", default=list) class Meta: model = Changeset - geo_field = 'bbox' - exclude = ('powerfull_editor', 'new_features') + geo_field = "bbox" + exclude = ("powerfull_editor", "new_features") class ChangesetSerializer(ChangesetSerializerToStaff): @@ -52,28 +60,25 @@ class ChangesetSerializer(ChangesetSerializerToStaff): 'powerfull_editor'. It doesn't list the SuspicionReasons and Tags whose 'is_visible' field is False. """ + reasons = SerializerMethodField() tags = SerializerMethodField() def get_reasons(self, obj): return BasicSuspicionReasonsSerializer( - obj.reasons.filter(is_visible=True), - many=True, - read_only=True - ).data + obj.reasons.filter(is_visible=True), many=True, read_only=True + ).data def get_tags(self, obj): return BasicTagSerializer( - obj.tags.filter(is_visible=True), - many=True, - read_only=True - ).data + obj.tags.filter(is_visible=True), many=True, read_only=True + ).data class UserWhitelistSerializer(ModelSerializer): class Meta: model = UserWhitelist - fields = ('whitelist_user',) + fields = ("whitelist_user",) class ChangesetListStatsSerializer(ListSerializer): @@ -84,42 +89,44 @@ def to_representation(self, data): checked_changesets = data.filter(checked=True) harmful_changesets = data.filter(harmful=True) - if self.context['request'].user.is_staff: - reasons = SuspicionReasons.objects.order_by('-name') - tags = Tag.objects.order_by('-name') + if self.context["request"].user.is_staff: + reasons = SuspicionReasons.objects.order_by("-name") + tags = Tag.objects.order_by("-name") else: - reasons = SuspicionReasons.objects.filter( - is_visible=True - ).order_by('-name') - tags = Tag.objects.filter(is_visible=True).order_by('-name') + reasons = SuspicionReasons.objects.filter(is_visible=True).order_by("-name") + tags = Tag.objects.filter(is_visible=True).order_by("-name") reasons_list = [ - {'name': reason.name, - 'changesets': data.filter(reasons=reason).count(), - 'checked_changesets': checked_changesets.filter(reasons=reason).count(), - 'harmful_changesets': harmful_changesets.filter(reasons=reason).count(), - } + { + "name": reason.name, + "changesets": data.filter(reasons=reason).count(), + "checked_changesets": checked_changesets.filter(reasons=reason).count(), + "harmful_changesets": harmful_changesets.filter(reasons=reason).count(), + } for reason in reasons - ] + ] tags_list = [ - {'name': tag.name, - 'changesets': data.filter(tags=tag).count(), - 'checked_changesets': checked_changesets.filter(tags=tag).count(), - 'harmful_changesets': harmful_changesets.filter(tags=tag).count(), - } + { + "name": tag.name, + "changesets": data.filter(tags=tag).count(), + "checked_changesets": checked_changesets.filter(tags=tag).count(), + "harmful_changesets": harmful_changesets.filter(tags=tag).count(), + } for tag in tags - ] + ] return { - 'changesets': data.count(), - 'checked_changesets': checked_changesets.count(), - 'harmful_changesets': harmful_changesets.count(), - 'users_with_harmful_changesets': harmful_changesets.values_list( - 'user', flat=True - ).distinct().count(), - 'reasons': reasons_list, - 'tags': tags_list - } + "changesets": data.count(), + "checked_changesets": checked_changesets.count(), + "harmful_changesets": harmful_changesets.count(), + "users_with_harmful_changesets": harmful_changesets.values_list( + "user", flat=True + ) + .distinct() + .count(), + "reasons": reasons_list, + "tags": tags_list, + } @property def data(self): @@ -135,26 +142,22 @@ class Meta: # that check features/changesets or add/remove SuspicionReasons and Tags class SuspicionReasonsChangesetSerializer(ModelSerializer): changesets = PrimaryKeyRelatedField( - many=True, - queryset=Changeset.objects.all(), - help_text='List of changesets ids.' - ) + many=True, queryset=Changeset.objects.all(), help_text="List of changesets ids." + ) class Meta: model = SuspicionReasons - fields = ('changesets',) + fields = ("changesets",) class ChangesetTagsSerializer(ModelSerializer): tags = PrimaryKeyRelatedField( - many=True, - queryset=Tag.objects.all(), - help_text='List of tags ids' - ) + many=True, queryset=Tag.objects.all(), help_text="List of tags ids" + ) class Meta: model = Changeset - fields = ('tags',) + fields = ("tags",) class ChangesetCommentSerializer(Serializer): @@ -162,5 +165,5 @@ class ChangesetCommentSerializer(Serializer): class ReviewedFeatureSerializer(Serializer): - type = ChoiceField(choices=['node', 'way', 'relation'], allow_blank=False) + type = ChoiceField(choices=["node", "way", "relation"], allow_blank=False) id = IntegerField() diff --git a/osmchadjango/changeset/tasks.py b/osmchadjango/changeset/tasks.py index 09048329..ead98ac5 100644 --- a/osmchadjango/changeset/tasks.py +++ b/osmchadjango/changeset/tasks.py @@ -1,5 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import print_function, unicode_literals from os.path import join from urllib.parse import quote import yaml @@ -22,24 +20,23 @@ def create_changeset(changeset_id): # remove suspicion_reasons ch_dict = ch.get_dict() - ch_dict.pop('suspicion_reasons') + ch_dict.pop("suspicion_reasons") # remove bbox field if it is not a valid geometry - if ch.bbox == 'GEOMETRYCOLLECTION EMPTY': - ch_dict.pop('bbox') + if ch.bbox == "GEOMETRYCOLLECTION EMPTY": + ch_dict.pop("bbox") # save changeset. changeset, created = Changeset.objects.update_or_create( - id=ch_dict['id'], - defaults=ch_dict - ) + id=ch_dict["id"], defaults=ch_dict + ) if ch.suspicion_reasons: for reason in ch.suspicion_reasons: reason, created = SuspicionReasons.objects.get_or_create(name=reason) reason.changesets.add(changeset) - print('{c[id]} created'.format(c=ch_dict)) + print(f"{ch_dict.get(id)} created") return changeset @@ -49,14 +46,14 @@ def get_filter_changeset_file(url, geojson_filter=settings.CHANGESETS_FILTER): GeoJSON file. """ cl = ChangesetList(url, geojson_filter) - group(create_changeset.s(c['id']) for c in cl.changesets)() + group(create_changeset.s(c["id"]) for c in cl.changesets)() def format_url(n): """Return the url of a replication file.""" n = str(n) - base_url = 'https://planet.openstreetmap.org/replication/changesets/' - return join(base_url, '00%s' % n[0], n[1:4], '%s.osm.gz' % n[4:]) + base_url = "https://planet.openstreetmap.org/replication/changesets/" + return join(base_url, "00%s" % n[0], n[1:4], "%s.osm.gz" % n[4:]) @shared_task @@ -70,13 +67,12 @@ def import_replications(start, end): def get_last_replication_id(): - """Get the id number of the last replication file available on Planet OSM. - """ + """Get the id number of the last replication file available on Planet OSM.""" state = requests.get( - 'https://planet.openstreetmap.org/replication/changesets/state.yaml' - ).content + "https://planet.openstreetmap.org/replication/changesets/state.yaml" + ).content state = yaml.load(state) - return state.get('sequence') + return state.get("sequence") @shared_task @@ -86,7 +82,7 @@ def fetch_latest(): FIXME: define error in except line """ try: - last_import = Import.objects.all().order_by('-end')[0].end + last_import = Import.objects.all().order_by("-end")[0].end except: last_import = None @@ -96,7 +92,7 @@ def fetch_latest(): start = last_import + 1 else: start = sequence - 1000 - print("Importing replications from %d to %d" % (start, sequence,)) + print(f"Importing replications from {start} to {sequence}") import_replications(start, sequence) @@ -104,33 +100,31 @@ class ChangesetCommentAPI(object): """Class that allows us to publish comments in changesets on the OpenStreetMap website. """ + def __init__(self, user, changeset_id): self.changeset_id = changeset_id user_token = user.social_auth.all().first().access_token self.client = OAuth1Session( settings.SOCIAL_AUTH_OPENSTREETMAP_KEY, client_secret=settings.SOCIAL_AUTH_OPENSTREETMAP_SECRET, - resource_owner_key=user_token['oauth_token'], - resource_owner_secret=user_token['oauth_token_secret'] - ) - self.url = 'https://api.openstreetmap.org/api/0.6/changeset/{}/comment/'.format( - changeset_id - ) + resource_owner_key=user_token["oauth_token"], + resource_owner_secret=user_token["oauth_token_secret"], + ) + self.url = ( + f"https://api.openstreetmap.org/api/0.6/changeset/{changeset_id}/comment/" + ) def post_comment(self, message=None): """Post comment to changeset.""" response = self.client.post( - self.url, - data='text={}'.format(quote(message)).encode("utf-8") - ) + self.url, data=f"text={quote(message)}".encode("utf-8") + ) if response.status_code == 200: - print( - 'Comment in the changeset {} posted successfully.'.format( - self.changeset_id - ) - ) - return {'success': True} + print(f"Comment in the changeset {self.changeset_id} posted successfully.") + return {"success": True} else: - print("""Some error occurred and it wasn't possible to post the - comment to the changeset {}.""".format(self.changeset_id)) - return {'success': False} + print( + f"""Some error occurred and it wasn't possible to post the + comment to the changeset {self.changeset_id}.""" + ) + return {"success": False} diff --git a/osmchadjango/changeset/throttling.py b/osmchadjango/changeset/throttling.py index a6c1c856..2531f180 100644 --- a/osmchadjango/changeset/throttling.py +++ b/osmchadjango/changeset/throttling.py @@ -2,7 +2,7 @@ class NonStaffUserThrottle(UserRateThrottle): - scope = 'non_staff_user' + scope = "non_staff_user" def get_cache_key(self, request, view): if request.user.is_authenticated: @@ -13,7 +13,4 @@ def get_cache_key(self, request, view): else: ident = self.get_ident(request) - return self.cache_format % { - 'scope': self.scope, - 'ident': ident - } + return self.cache_format % {"scope": self.scope, "ident": ident} diff --git a/osmchadjango/changeset/urls.py b/osmchadjango/changeset/urls.py index 101d1db5..df06e642 100644 --- a/osmchadjango/changeset/urls.py +++ b/osmchadjango/changeset/urls.py @@ -1,138 +1,108 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import, unicode_literals - -from django.urls import re_path, path +from django.urls import path from . import views -app_name = 'changeset' +app_name = "changeset" urlpatterns = [ - re_path( - r'^changesets/$', - view=views.ChangesetListAPIView.as_view(), - name='list' - ), - re_path( - r'^changesets/suspect/$', + path("changesets/", view=views.ChangesetListAPIView.as_view(), name="list"), + path( + "changesets/suspect/", view=views.SuspectChangesetListAPIView.as_view(), - name='suspect-list' + name="suspect-list", ), - re_path( - r'^changesets/no-suspect/$', + path( + "changesets/no-suspect/", view=views.NoSuspectChangesetListAPIView.as_view(), - name='no-suspect-list' + name="no-suspect-list", ), - re_path( - r'^changesets/harmful/$', + path( + "changesets/harmful/", view=views.HarmfulChangesetListAPIView.as_view(), - name='harmful-list' + name="harmful-list", ), - re_path( - r'^changesets/no-harmful/$', + path( + "changesets/no-harmful/", view=views.NoHarmfulChangesetListAPIView.as_view(), - name='no-harmful-list' + name="no-harmful-list", ), - re_path( - r'^changesets/checked/$', + path( + "changesets/checked/", view=views.CheckedChangesetListAPIView.as_view(), - name='checked-list' + name="checked-list", ), - re_path( - r'^changesets/unchecked/$', + path( + "changesets/unchecked/", view=views.UncheckedChangesetListAPIView.as_view(), - name='unchecked-list' + name="unchecked-list", ), - re_path( - r'^changesets/(?P\d+)/$', + path( + "changesets//", view=views.ChangesetDetailAPIView.as_view(), - name='detail' - ), - re_path( - r'^changesets/(?P\w+)/tag-changes/$', - view=views.SetChangesetTagChangesAPIView.as_view({'post': 'set_tag_changes'}), - name='set-tag-changes' + name="detail", ), - re_path( - r'^changesets/(?P\w+)/set-harmful/$', - view=views.CheckChangeset.as_view({'put': 'set_harmful'}), - name='set-harmful' + path( + "changesets//tag-changes/", + view=views.SetChangesetTagChangesAPIView.as_view({"post": "set_tag_changes"}), + name="set-tag-changes", ), - re_path( - r'^changesets/(?P\w+)/set-good/$', - view=views.CheckChangeset.as_view({'put': 'set_good'}), - name='set-good' + path( + "changesets//set-harmful/", + view=views.CheckChangeset.as_view({"put": "set_harmful"}), + name="set-harmful", ), - re_path( - r'^changesets/(?P\w+)/uncheck/$', - view=views.uncheck_changeset, - name='uncheck' + path( + "changesets//set-good/", + view=views.CheckChangeset.as_view({"put": "set_good"}), + name="set-good", ), + path("changesets//uncheck/", view=views.uncheck_changeset, name="uncheck"), path( - 'changesets//review-feature/-/', + "changesets//review-feature/-/", view=views.ReviewFeature.as_view( - {'put': 'set_harmful_feature', 'delete': 'remove_harmful_feature'} - ), - name='review-harmful-feature' + {"put": "set_harmful_feature", "delete": "remove_harmful_feature"} + ), + name="review-harmful-feature", ), - re_path( - r'^changesets/(?P\w+)/tags/(?P\w+)/$', + path( + "changesets//tags//", view=views.AddRemoveChangesetTagsAPIView.as_view( - {'post': 'add_tag', 'delete': 'remove_tag'} - ), - name='tags' - ), - re_path( - r'^changesets/(?P\w+)/comment/$', - view=views.ChangesetCommentAPIView.as_view( - {'post': 'post_comment'} - ), - name='comment' - ), - re_path( - r'^changesets/add-feature/$', - view=views.add_feature, - name='add-feature' - ), - re_path( - r'^features/create/$', - view=views.add_feature_v1, - name='add-feature-v1' - ), - re_path( - r'^whitelist-user/$', + {"post": "add_tag", "delete": "remove_tag"} + ), + name="tags", + ), + path( + "changesets//comment/", + view=views.ChangesetCommentAPIView.as_view({"post": "post_comment"}), + name="comment", + ), + path("changesets/add-feature/", view=views.add_feature, name="add-feature"), + path("features/create/", view=views.add_feature_v1, name="add-feature-v1"), + path( + "whitelist-user/", view=views.UserWhitelistListCreateAPIView.as_view(), - name='whitelist-user' + name="whitelist-user", ), path( - 'whitelist-user//', + "whitelist-user//", view=views.UserWhitelistDestroyAPIView.as_view(), - name='delete-whitelist-user' + name="delete-whitelist-user", ), - re_path( - r'^suspicion-reasons/$', + path( + "suspicion-reasons/", view=views.SuspicionReasonsListAPIView.as_view(), - name='suspicion-reasons-list' - ), - re_path( - r'^suspicion-reasons/(?P\w+)/changesets/$', - view=views.AddRemoveChangesetReasonsAPIView.as_view( - {'post': 'add_reason_to_changesets', 'delete': 'remove_reason_from_changesets'} - ), - name='changeset-reasons' - ), - re_path( - r'^tags/$', - view=views.TagListAPIView.as_view(), - name='tags-list' - ), - re_path( - r'^stats/$', - view=views.ChangesetStatsAPIView.as_view(), - name='stats' + name="suspicion-reasons-list", ), path( - 'user-stats//', - view=views.user_stats, - name='user-stats' - ), + "suspicion-reasons//changesets/", + view=views.AddRemoveChangesetReasonsAPIView.as_view( + { + "post": "add_reason_to_changesets", + "delete": "remove_reason_from_changesets", + } + ), + name="changeset-reasons", + ), + path("tags/", view=views.TagListAPIView.as_view(), name="tags-list"), + path("stats/", view=views.ChangesetStatsAPIView.as_view(), name="stats"), + path("user-stats//", view=views.user_stats, name="user-stats"), ] diff --git a/osmchadjango/changeset/views.py b/osmchadjango/changeset/views.py index 63e0c42a..f9a88cfa 100644 --- a/osmchadjango/changeset/views.py +++ b/osmchadjango/changeset/views.py @@ -1,8 +1,4 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - from django.utils import timezone -from django.utils.translation import ugettext, ugettext_lazy as _ from django.db.utils import IntegrityError from django.db import connection from django.conf import settings @@ -10,12 +6,19 @@ import django_filters.rest_framework from rest_framework import status from rest_framework.decorators import ( - api_view, parser_classes, permission_classes, action, throttle_classes - ) + api_view, + parser_classes, + permission_classes, + action, + throttle_classes, +) from rest_framework.generics import ( - ListAPIView, ListCreateAPIView, RetrieveAPIView, get_object_or_404, - DestroyAPIView - ) + ListAPIView, + ListCreateAPIView, + RetrieveAPIView, + get_object_or_404, + DestroyAPIView, +) from rest_framework.parsers import JSONParser, MultiPartParser, FormParser from rest_framework.permissions import IsAuthenticated, IsAdminUser from rest_framework.response import Response @@ -29,25 +32,31 @@ from .models import Changeset, UserWhitelist, SuspicionReasons, Tag from .filters import ChangesetFilter from .serializers import ( - ChangesetSerializer, ChangesetSerializerToStaff, ChangesetStatsSerializer, - ChangesetTagsSerializer, SuspicionReasonsChangesetSerializer, - SuspicionReasonsSerializer, UserWhitelistSerializer, - TagSerializer, ChangesetCommentSerializer, ReviewedFeatureSerializer - ) + ChangesetSerializer, + ChangesetSerializerToStaff, + ChangesetStatsSerializer, + ChangesetTagsSerializer, + SuspicionReasonsChangesetSerializer, + SuspicionReasonsSerializer, + UserWhitelistSerializer, + TagSerializer, + ChangesetCommentSerializer, + ReviewedFeatureSerializer, +) from .tasks import ChangesetCommentAPI from .throttling import NonStaffUserThrottle -from ..roulette_integration.utils import push_feature_to_maproulette -from ..roulette_integration.models import ChallengeIntegration +from osmchadjango.roulette_integration.utils import push_feature_to_maproulette +from osmchadjango.roulette_integration.models import ChallengeIntegration class StandardResultsSetPagination(GeoJsonPagination): page_size = 50 - page_size_query_param = 'page_size' + page_size_query_param = "page_size" max_page_size = 500 -class PaginatedCSVRenderer (CSVRenderer): - results_field = 'features' +class PaginatedCSVRenderer(CSVRenderer): + results_field = "features" def render(self, data, *args, **kwargs): if not isinstance(data, list): @@ -65,19 +74,20 @@ class ChangesetListAPIView(ListAPIView): default pagination returns 50 objects by page. """ - queryset = Changeset.objects.all().select_related( - 'check_user' - ).prefetch_related( - 'tags', 'reasons', 'features', 'features__reasons' - ).exclude(user="") + queryset = ( + Changeset.objects.all() + .select_related("check_user") + .prefetch_related("tags", "reasons", "features", "features__reasons") + .exclude(user="") + ) permission_classes = (IsAuthenticated,) pagination_class = StandardResultsSetPagination renderer_classes = (JSONRenderer, BrowsableAPIRenderer, PaginatedCSVRenderer) - bbox_filter_field = 'bbox' + bbox_filter_field = "bbox" filter_backends = ( InBBoxFilter, django_filters.rest_framework.DjangoFilterBackend, - ) + ) bbox_filter_include_overlapping = True filter_class = ChangesetFilter @@ -90,10 +100,13 @@ def get_serializer_class(self): class ChangesetDetailAPIView(RetrieveAPIView): """Return details of a Changeset.""" + permission_classes = (IsAuthenticated,) - queryset = Changeset.objects.all().select_related( - 'check_user' - ).prefetch_related('tags', 'reasons') + queryset = ( + Changeset.objects.all() + .select_related("check_user") + .prefetch_related("tags", "reasons") + ) def get_serializer_class(self): if self.request.user.is_staff: @@ -158,6 +171,7 @@ def get_queryset(self): class SuspicionReasonsListAPIView(ListAPIView): """List SuspicionReasons.""" + serializer_class = SuspicionReasonsSerializer def get_queryset(self): @@ -172,7 +186,7 @@ class AddRemoveChangesetReasonsAPIView(ModelViewSet): serializer_class = SuspicionReasonsChangesetSerializer permission_classes = (IsAdminUser,) - @action(detail=True, methods=['post']) + @action(detail=True, methods=["post"]) def add_reason_to_changesets(self, request, pk): """This endpoint allows us to add Suspicion Reasons to changesets in a batch. The use of this endpoint is restricted to staff users. The ids of @@ -181,18 +195,15 @@ def add_reason_to_changesets(self, request, pk): reason = self.get_object() serializer = self.get_serializer(data=request.data) if serializer.is_valid(): - reason.changesets.add(*serializer.data['changesets']) + reason.changesets.add(*serializer.data["changesets"]) return Response( - {'detail': 'Suspicion Reasons added to changesets.'}, - status=status.HTTP_200_OK - ) + {"detail": "Suspicion Reasons added to changesets."}, + status=status.HTTP_200_OK, + ) else: - return Response( - serializer.errors, - status=status.HTTP_400_BAD_REQUEST - ) + return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) - @action(detail=True, methods=['delete']) + @action(detail=True, methods=["delete"]) def remove_reason_from_changesets(self, request, pk): """This endpoint allows us to remove Suspicion Reasons from changesets in a batch. The use of this endpoint is restricted to staff users. The @@ -201,20 +212,18 @@ def remove_reason_from_changesets(self, request, pk): reason = self.get_object() serializer = self.get_serializer(data=request.data) if serializer.is_valid(): - reason.changesets.remove(*serializer.data['changesets']) + reason.changesets.remove(*serializer.data["changesets"]) return Response( - {'detail': 'Suspicion Reasons removed from changesets.'}, - status=status.HTTP_200_OK - ) + {"detail": "Suspicion Reasons removed from changesets."}, + status=status.HTTP_200_OK, + ) else: - return Response( - serializer.errors, - status=status.HTTP_400_BAD_REQUEST - ) + return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) class TagListAPIView(ListAPIView): """List Tags.""" + serializer_class = TagSerializer def get_queryset(self): @@ -231,50 +240,52 @@ class ReviewFeature(ModelViewSet): def add_reviewed_feature(self, type, id, harmful): changeset = self.get_object() - is_feature_present = len([f for f in changeset.reviewed_features if f["id"] == f"{type}-{id}"]) > 0 + is_feature_present = ( + len([f for f in changeset.reviewed_features if f["id"] == f"{type}-{id}"]) + > 0 + ) if is_feature_present: return Response( f"Feature {type}-{id} is already added to the changeset", - status=status.HTTP_400_BAD_REQUEST - ) + status=status.HTTP_400_BAD_REQUEST, + ) - if changeset.uid in self.request.user.social_auth.values_list('uid', flat=True): + if changeset.uid in self.request.user.social_auth.values_list("uid", flat=True): return Response( - {'detail': 'User can not check features on his own changeset.'}, - status=status.HTTP_403_FORBIDDEN - ) + {"detail": "User can not check features on his own changeset."}, + status=status.HTTP_403_FORBIDDEN, + ) changeset.reviewed_features.append( {"id": f"{type}-{id}", "user": self.request.user.username} ) changeset.save(update_fields=["reviewed_features"]) return Response( - {'detail': f"Feature {type}-{id} added to changeset review list."}, - status=status.HTTP_200_OK - ) + {"detail": f"Feature {type}-{id} added to changeset review list."}, + status=status.HTTP_200_OK, + ) - @action(detail=True, methods=['put']) + @action(detail=True, methods=["put"]) def set_harmful_feature(self, request, pk, type, id): serializer = ReviewedFeatureSerializer(data={"type": type, "id": id}) if serializer.is_valid(): return self.add_reviewed_feature(type, id, True) else: - return Response( - serializer.errors, - status=status.HTTP_400_BAD_REQUEST - ) + return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) - @action(detail=True, methods=['delete']) + @action(detail=True, methods=["delete"]) def remove_harmful_feature(self, request, pk, type, id): serializer = ReviewedFeatureSerializer(data={"type": type, "id": id}) if serializer.is_valid(): changeset = self.get_object() - if changeset.uid in self.request.user.social_auth.values_list('uid', flat=True): + if changeset.uid in self.request.user.social_auth.values_list( + "uid", flat=True + ): return Response( - {'detail': 'User can not check features on his own changeset.'}, - status=status.HTTP_403_FORBIDDEN - ) + {"detail": "User can not check features on his own changeset."}, + status=status.HTTP_403_FORBIDDEN, + ) feature_index = None for i, feature in enumerate(changeset.reviewed_features): @@ -285,19 +296,18 @@ def remove_harmful_feature(self, request, pk, type, id): changeset.reviewed_features.pop(feature_index) changeset.save(update_fields=["reviewed_features"]) return Response( - {'detail': f"Feature {type}-{id} removed from changeset review list."}, - status=status.HTTP_200_OK - ) + { + "detail": f"Feature {type}-{id} removed from changeset review list." + }, + status=status.HTTP_200_OK, + ) else: return Response( f"Feature {type}-{id} is not present on the changeset review list.", - status=status.HTTP_400_BAD_REQUEST - ) - else: - return Response( - serializer.errors, - status=status.HTTP_400_BAD_REQUEST + status=status.HTTP_400_BAD_REQUEST, ) + else: + return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) class CheckChangeset(ModelViewSet): @@ -313,15 +323,13 @@ def update_changeset(self, changeset, request, harmful): changeset.harmful = harmful changeset.check_user = request.user changeset.check_date = timezone.now() - changeset.save( - update_fields=['checked', 'harmful', 'check_user', 'check_date'] - ) + changeset.save(update_fields=["checked", "harmful", "check_user", "check_date"]) return Response( - {'detail': 'Changeset marked as {}.'.format('harmful' if harmful else 'good')}, - status=status.HTTP_200_OK - ) + {"detail": f"Changeset marked as {'harmful' if harmful else 'good'}."}, + status=status.HTTP_200_OK, + ) - @action(detail=True, methods=['put']) + @action(detail=True, methods=["put"]) def set_harmful(self, request, pk): """Mark a changeset as harmful. You can set the tags of the changeset by sending a list of tag ids inside a field named 'tags' in the request @@ -331,26 +339,23 @@ def set_harmful(self, request, pk): changeset = self.get_object() if changeset.checked: return Response( - {'detail': 'Changeset was already checked.'}, - status=status.HTTP_403_FORBIDDEN - ) - if changeset.uid in request.user.social_auth.values_list('uid', flat=True): + {"detail": "Changeset was already checked."}, + status=status.HTTP_403_FORBIDDEN, + ) + if changeset.uid in request.user.social_auth.values_list("uid", flat=True): return Response( - {'detail': 'User can not check his own changeset.'}, - status=status.HTTP_403_FORBIDDEN - ) + {"detail": "User can not check his own changeset."}, + status=status.HTTP_403_FORBIDDEN, + ) if request.data: serializer = ChangesetTagsSerializer(data=request.data) if serializer.is_valid(): - changeset.tags.set(serializer.data['tags']) + changeset.tags.set(serializer.data["tags"]) else: - return Response( - serializer.errors, - status=status.HTTP_400_BAD_REQUEST - ) + return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) return self.update_changeset(changeset, request, harmful=True) - @action(detail=True, methods=['put']) + @action(detail=True, methods=["put"]) def set_good(self, request, pk): """Mark a changeset as good. You can set the tags of the changeset by sending a list of tag ids inside a field named 'tags' in the request @@ -360,58 +365,50 @@ def set_good(self, request, pk): changeset = self.get_object() if changeset.checked: return Response( - {'detail': 'Changeset was already checked.'}, - status=status.HTTP_403_FORBIDDEN - ) - if changeset.uid in request.user.social_auth.values_list('uid', flat=True): + {"detail": "Changeset was already checked."}, + status=status.HTTP_403_FORBIDDEN, + ) + if changeset.uid in request.user.social_auth.values_list("uid", flat=True): return Response( - {'detail': 'User can not check his own changeset.'}, - status=status.HTTP_403_FORBIDDEN - ) + {"detail": "User can not check his own changeset."}, + status=status.HTTP_403_FORBIDDEN, + ) if request.data: serializer = ChangesetTagsSerializer(data=request.data) if serializer.is_valid(): - changeset.tags.set(serializer.data['tags']) + changeset.tags.set(serializer.data["tags"]) else: - return Response( - serializer.errors, - status=status.HTTP_400_BAD_REQUEST - ) + return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) return self.update_changeset(changeset, request, harmful=False) -@api_view(['PUT']) +@api_view(["PUT"]) @parser_classes((JSONParser, MultiPartParser, FormParser)) @permission_classes((IsAuthenticated,)) def uncheck_changeset(request, pk): """Mark a changeset as unchecked. You don't need to send data, just an empty PUT request.""" instance = get_object_or_404( - Changeset.objects.all().select_related('check_user'), - pk=pk - ) + Changeset.objects.all().select_related("check_user"), pk=pk + ) if instance.checked is False: return Response( - {'detail': 'Changeset is not checked.'}, - status=status.HTTP_403_FORBIDDEN - ) + {"detail": "Changeset is not checked."}, status=status.HTTP_403_FORBIDDEN + ) elif request.user == instance.check_user or request.user.is_staff: instance.checked = False instance.harmful = None instance.check_user = None instance.check_date = None - instance.save( - update_fields=['checked', 'harmful', 'check_user', 'check_date'] - ) + instance.save(update_fields=["checked", "harmful", "check_user", "check_date"]) return Response( - {'detail': 'Changeset marked as unchecked.'}, - status=status.HTTP_200_OK - ) + {"detail": "Changeset marked as unchecked."}, status=status.HTTP_200_OK + ) else: return Response( - {'detail': 'User does not have permission to uncheck this changeset.'}, - status=status.HTTP_403_FORBIDDEN - ) + {"detail": "User does not have permission to uncheck this changeset."}, + status=status.HTTP_403_FORBIDDEN, + ) class AddRemoveChangesetTagsAPIView(ModelViewSet): @@ -421,7 +418,7 @@ class AddRemoveChangesetTagsAPIView(ModelViewSet): # in docs schema generation. serializer_class = ChangesetStatsSerializer - @action(detail=True, methods=['post']) + @action(detail=True, methods=["post"]) def add_tag(self, request, pk, tag_pk): """Add a tag to a changeset. If the changeset is unchecked, any user can add tags. After the changeset got checked, only staff users and the user @@ -431,25 +428,27 @@ def add_tag(self, request, pk, tag_pk): changeset = self.get_object() tag = get_object_or_404(Tag.objects.filter(for_changeset=True), pk=tag_pk) - if changeset.uid in request.user.social_auth.values_list('uid', flat=True): + if changeset.uid in request.user.social_auth.values_list("uid", flat=True): return Response( - {'detail': 'User can not add tags to his own changeset.'}, - status=status.HTTP_403_FORBIDDEN - ) + {"detail": "User can not add tags to his own changeset."}, + status=status.HTTP_403_FORBIDDEN, + ) if changeset.checked and ( - request.user != changeset.check_user and not request.user.is_staff): + request.user != changeset.check_user and not request.user.is_staff + ): return Response( - {'detail': 'User can not add tags to a changeset checked by another user.'}, - status=status.HTTP_403_FORBIDDEN - ) + { + "detail": "User can not add tags to a changeset checked by another user." + }, + status=status.HTTP_403_FORBIDDEN, + ) changeset.tags.add(tag) return Response( - {'detail': 'Tag added to the changeset.'}, - status=status.HTTP_200_OK - ) + {"detail": "Tag added to the changeset."}, status=status.HTTP_200_OK + ) - @action(detail=True, methods=['delete']) + @action(detail=True, methods=["delete"]) def remove_tag(self, request, pk, tag_pk): """Remove a tag from a changeset. If the changeset is unchecked, any user can remove tags. After the changeset got checked, only staff users and @@ -459,28 +458,30 @@ def remove_tag(self, request, pk, tag_pk): changeset = self.get_object() tag = get_object_or_404(Tag.objects.all(), pk=tag_pk) - if changeset.uid in request.user.social_auth.values_list('uid', flat=True): + if changeset.uid in request.user.social_auth.values_list("uid", flat=True): return Response( - {'detail': 'User can not remove tags from his own changeset.'}, - status=status.HTTP_403_FORBIDDEN - ) + {"detail": "User can not remove tags from his own changeset."}, + status=status.HTTP_403_FORBIDDEN, + ) if changeset.checked and ( - request.user != changeset.check_user and not request.user.is_staff): + request.user != changeset.check_user and not request.user.is_staff + ): return Response( - {'detail': 'User can not remove tags from a changeset checked by another user.'}, - status=status.HTTP_403_FORBIDDEN - ) + { + "detail": "User can not remove tags from a changeset checked by another user." + }, + status=status.HTTP_403_FORBIDDEN, + ) changeset.tags.remove(tag) return Response( - {'detail': 'Tag removed from the changeset.'}, - status=status.HTTP_200_OK - ) + {"detail": "Tag removed from the changeset."}, status=status.HTTP_200_OK + ) class WhitelistValidationException(APIException): status_code = status.HTTP_403_FORBIDDEN - default_detail = 'The user was already whitelisted by the request user.' + default_detail = "The user was already whitelisted by the request user." class UserWhitelistListCreateAPIView(ListCreateAPIView): @@ -491,6 +492,7 @@ class UserWhitelistListCreateAPIView(ListCreateAPIView): post: Add a user to the whitelist of the request user. """ + serializer_class = UserWhitelistSerializer permission_classes = (IsAuthenticated,) @@ -509,9 +511,10 @@ def perform_create(self, serializer): class UserWhitelistDestroyAPIView(DestroyAPIView): """Delete a user from the whitelist of the request user.""" + serializer_class = UserWhitelistSerializer permission_classes = (IsAuthenticated,) - lookup_field = 'whitelist_user' + lookup_field = "whitelist_user" def get_queryset(self): return UserWhitelist.objects.filter(user=self.request.user) @@ -524,21 +527,24 @@ class ChangesetStatsAPIView(ListAPIView): It's possible to filter the changesets using the same filter parameters of the changeset list endpoint. """ - queryset = Changeset.objects.all().select_related( - 'check_user' - ).prefetch_related('tags', 'reasons') + + queryset = ( + Changeset.objects.all() + .select_related("check_user") + .prefetch_related("tags", "reasons") + ) serializer_class = ChangesetStatsSerializer renderer_classes = (JSONRenderer, BrowsableAPIRenderer, PaginatedCSVRenderer) - bbox_filter_field = 'bbox' + bbox_filter_field = "bbox" filter_backends = ( InBBoxFilter, django_filters.rest_framework.DjangoFilterBackend, - ) + ) bbox_filter_include_overlapping = True filter_class = ChangesetFilter -@api_view(['GET']) +@api_view(["GET"]) @permission_classes((IsAuthenticated,)) def user_stats(request, uid): """Get stats about an OSM user in the OSMCHA history. @@ -558,8 +564,8 @@ def user_stats(request, uid): instance = { "changesets_in_osmcha": total, "checked_changesets": checked, - "harmful_changesets": harmful - } + "harmful_changesets": harmful, + } return Response(instance) @@ -568,123 +574,108 @@ class ChangesetCommentAPIView(ModelViewSet): permission_classes = (IsAuthenticated,) serializer_class = ChangesetCommentSerializer - @action(detail=True, methods=['post']) + @action(detail=True, methods=["post"]) def post_comment(self, request, pk): "Post a comment to a changeset in the OpenStreetMap website." self.changeset = self.get_object() serializer = self.get_serializer(data=request.data) if serializer.is_valid(): if settings.ENABLE_POST_CHANGESET_COMMENTS: - changeset_comment = ChangesetCommentAPI( - request.user, - self.changeset.id - ) + changeset_comment = ChangesetCommentAPI(request.user, self.changeset.id) comment_response = changeset_comment.post_comment( - self.add_footer(serializer.data['comment']) - ) - if comment_response.get('success'): + self.add_footer(serializer.data["comment"]) + ) + if comment_response.get("success"): return Response( - {'detail': 'Changeset comment posted succesfully.'}, - status=status.HTTP_201_CREATED - ) + {"detail": "Changeset comment posted succesfully."}, + status=status.HTTP_201_CREATED, + ) else: return Response( - {'detail': 'Changeset comment failed.'}, - status=status.HTTP_400_BAD_REQUEST - ) + {"detail": "Changeset comment failed."}, + status=status.HTTP_400_BAD_REQUEST, + ) else: return Response( - {'detail': 'Changeset comment is not enabled.'}, - status=status.HTTP_403_FORBIDDEN - ) - else: - return Response( - serializer.errors, - status=status.HTTP_400_BAD_REQUEST + {"detail": "Changeset comment is not enabled."}, + status=status.HTTP_403_FORBIDDEN, ) + else: + return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) def add_footer(self, message): status = "" if self.changeset.checked and self.changeset.harmful is not None: - status = "#REVIEWED_{} #OSMCHA".format( - 'BAD' if self.changeset.harmful else 'GOOD' - ) - return """{} + status = f"#REVIEWED_{'BAD' if self.changeset.harmful else 'GOOD'} #OSMCHA" + return f"""{message} --- - {} - Published using OSMCha: https://osmcha.org/changesets/{} - """.format(message, status, self.changeset.id) + {status} + Published using OSMCha: https://osmcha.org/changesets/{self.changeset.id} + """ def validate_feature(feature): - required_fields = ['changeset', 'osm_id', 'osm_type', 'reasons'] + required_fields = ["changeset", "osm_id", "osm_type", "reasons"] missing_fields = [i for i in required_fields if i not in feature.keys()] # Check for missing required fields if len(missing_fields): - message = 'Request data is missing the following fields {}.'.format( - ', '.join(missing_fields) - ) - return Response( - {'detail': message}, - status=status.HTTP_400_BAD_REQUEST - ) + message = ( + f"Request data is missing the following fields {', '.join(missing_fields)}." + ) + return Response({"detail": message}, status=status.HTTP_400_BAD_REQUEST) # validate id and changeset fields try: - int(feature.get('osm_id')) - int(feature.get('changeset')) + int(feature.get("osm_id")) + int(feature.get("changeset")) except ValueError: return Response( - {'detail': 'osm_id or changeset values are not an integer.'}, - status=status.HTTP_400_BAD_REQUEST - ) + {"detail": "osm_id or changeset values are not an integer."}, + status=status.HTTP_400_BAD_REQUEST, + ) # validate osm_type - if feature.get('osm_type') not in ['node', 'way', 'relation']: + if feature.get("osm_type") not in ["node", "way", "relation"]: return Response( - {'detail': 'osm_type value should be "node", "way" or "relation".'}, - status=status.HTTP_400_BAD_REQUEST - ) + {"detail": 'osm_type value should be "node", "way" or "relation".'}, + status=status.HTTP_400_BAD_REQUEST, + ) # validate reasons - if type(feature.get('reasons')) != list: + if type(feature.get("reasons")) != list: return Response( - {'detail': 'reasons value should be a list.'}, - status=status.HTTP_400_BAD_REQUEST - ) + {"detail": "reasons value should be a list."}, + status=status.HTTP_400_BAD_REQUEST, + ) def filter_primary_tags(feature): PRIMARY_TAGS = [ - 'aerialway', - 'aeroway', - 'amenity', - 'barrier', - 'boundary', - 'building', - 'craft', - 'emergency', - 'geological', - 'highway', - 'historic', - 'landuse', - 'leisure', - 'man_made', - 'military', - 'natural', - 'office', - 'place', - 'power', - 'public_transport', - 'railway', - 'route', - 'shop', - 'tourism', - 'waterway' - ] - tags = feature.get('primary_tags', {}) - [ - tags.pop(key) - for key in list(tags.keys()) - if key not in PRIMARY_TAGS + "aerialway", + "aeroway", + "amenity", + "barrier", + "boundary", + "building", + "craft", + "emergency", + "geological", + "highway", + "historic", + "landuse", + "leisure", + "man_made", + "military", + "natural", + "office", + "place", + "power", + "public_transport", + "railway", + "route", + "shop", + "tourism", + "waterway", ] + tags = feature.get("primary_tags", {}) + [tags.pop(key) for key in list(tags.keys()) if key not in PRIMARY_TAGS] return tags @@ -695,22 +686,22 @@ class SetChangesetTagChangesAPIView(ModelViewSet): # in docs schema generation. serializer_class = ChangesetStatsSerializer - @action(detail=True, methods=['post']) + @action(detail=True, methods=["post"]) def set_tag_changes(self, request, pk): """Update the tag_changes field of a Changeset""" print(self.request.data) if self.validate_tag_changes(self.request.data) is False: return Response( - {'detail': 'Payload does not match validation rules.'}, - status=status.HTTP_400_BAD_REQUEST - ) + {"detail": "Payload does not match validation rules."}, + status=status.HTTP_400_BAD_REQUEST, + ) changeset = self.get_object() changeset.tag_changes = request.data - changeset.save(update_fields=['tag_changes']) + changeset.save(update_fields=["tag_changes"]) return Response( - {'detail': 'Updated tag_changes field of changeset.'}, - status=status.HTTP_200_OK - ) + {"detail": "Updated tag_changes field of changeset."}, + status=status.HTTP_200_OK, + ) def validate_tag_changes(self, tag_changes): """Check the integrity of the tag_changes payload.""" @@ -725,7 +716,7 @@ def validate_tag_changes(self, tag_changes): return True -@api_view(['POST']) +@api_view(["POST"]) @throttle_classes((NonStaffUserThrottle,)) @parser_classes((JSONParser, MultiPartParser, FormParser)) @permission_classes((IsAuthenticated, IsAdminUser)) @@ -738,7 +729,7 @@ def add_feature(request): """ feature = request.data - changeset_fields_to_update = ['new_features'] + changeset_fields_to_update = ["new_features"] # validate data validation = validate_feature(feature) @@ -746,10 +737,10 @@ def add_feature(request): return validation # Get reasons to add to changeset and define if it changeset will be suspect - suspicions = feature.get('reasons') + suspicions = feature.get("reasons") has_visible_features = False + reasons = set() if suspicions: - reasons = set() for suspicion in suspicions: try: reason_id = int(suspicion) @@ -758,68 +749,60 @@ def add_feature(request): if reason.is_visible: has_visible_features = True except (ValueError, SuspicionReasons.DoesNotExist): - reason, created = SuspicionReasons.objects.get_or_create( - name=suspicion - ) + reason, created = SuspicionReasons.objects.get_or_create(name=suspicion) reasons.add(reason) if reason.is_visible: has_visible_features = True - changeset_defaults = { - 'is_suspect': has_visible_features - } + changeset_defaults = {"is_suspect": has_visible_features} changeset, created = Changeset.objects.get_or_create( - id=feature.get('changeset'), - defaults=changeset_defaults - ) + id=feature.get("changeset"), defaults=changeset_defaults + ) if type(changeset.new_features) is not list: changeset.new_features = [] elif len(changeset.new_features) > 0: for i, f in enumerate(changeset.new_features): - if f['url'] == '{}-{}'.format(feature['osm_type'], feature['osm_id']): - f['reasons'] = list(set(f['reasons'] + [i.id for i in reasons])) + if f["url"] == f"{feature['osm_type']}-{ feature['osm_id']}": + f["reasons"] = list(set(f["reasons"] + [i.id for i in reasons])) changeset.save(update_fields=changeset_fields_to_update) add_reasons_to_changeset(changeset, reasons) return Response( - {'detail': 'Feature added to changeset.'}, - status=status.HTTP_200_OK - ) + {"detail": "Feature added to changeset."}, status=status.HTTP_200_OK + ) fields_to_save = [ - 'osm_id', 'version', 'reasons', 'name', 'note', 'primary_tags', 'url' - ] - feature['url'] = '{}-{}'.format( - feature.get('osm_type'), - feature.get('osm_id') - ) - feature['reasons'] = [i.id for i in reasons] + "osm_id", + "version", + "reasons", + "name", + "note", + "primary_tags", + "url", + ] + feature["url"] = f"{feature.get('osm_type')}-{feature.get('osm_id')}" + feature["reasons"] = [i.id for i in reasons] primary_tags = filter_primary_tags(feature) if len(primary_tags.items()): - feature['primary_tags'] = primary_tags + feature["primary_tags"] = primary_tags [feature.pop(k) for k in list(feature.keys()) if k not in fields_to_save] changeset.new_features.append(feature) if not changeset.is_suspect and has_visible_features: changeset.is_suspect = True - changeset_fields_to_update.append('is_suspect') + changeset_fields_to_update.append("is_suspect") changeset.save(update_fields=changeset_fields_to_update) - print( - 'Changeset {} {}'.format( - changeset.id, 'created' if created else 'updated' - ) - ) + print(f"Changeset {changeset.id} {'created' if created else 'updated'}") add_reasons_to_changeset(changeset, reasons) return Response( - {'detail': 'Feature added to changeset.'}, - status=status.HTTP_200_OK - ) + {"detail": "Feature added to changeset."}, status=status.HTTP_200_OK + ) -@api_view(['POST']) +@api_view(["POST"]) @throttle_classes((NonStaffUserThrottle,)) @parser_classes((JSONParser, MultiPartParser, FormParser)) @permission_classes((IsAuthenticated, IsAdminUser)) @@ -828,20 +811,20 @@ def add_feature_v1(request): previous format of features that was generated by vandalism-dynamosm. """ feature = {} - feature['osm_id'] = request.data['properties']['osm:id'] - feature['changeset'] = request.data['properties']['osm:changeset'] - feature['osm_type'] = request.data['properties']['osm:type'] - feature['version'] = request.data['properties']['osm:version'] - feature['primary_tags'] = request.data['properties'] - feature['reasons'] = [ - i.get('reason') for i in request.data['properties'].get('suspicions') - ] - if request.data['properties'].get('name'): - feature['name'] = request.data['properties'].get('name') - if request.data['properties'].get('osmcha:note'): - feature['note'] = request.data['properties'].get('osmcha:note') - - changeset_fields_to_update = ['new_features'] + feature["osm_id"] = request.data["properties"]["osm:id"] + feature["changeset"] = request.data["properties"]["osm:changeset"] + feature["osm_type"] = request.data["properties"]["osm:type"] + feature["version"] = request.data["properties"]["osm:version"] + feature["primary_tags"] = request.data["properties"] + feature["reasons"] = [ + i.get("reason") for i in request.data["properties"].get("suspicions") + ] + if request.data["properties"].get("name"): + feature["name"] = request.data["properties"].get("name") + if request.data["properties"].get("osmcha:note"): + feature["note"] = request.data["properties"].get("osmcha:note") + + changeset_fields_to_update = ["new_features"] # validate data validation = validate_feature(feature) @@ -849,10 +832,10 @@ def add_feature_v1(request): return validation # Get reasons to add to changeset and define if it changeset will be suspect - suspicions = feature.get('reasons') + suspicions = feature.get("reasons") has_visible_features = False + reasons = set() if suspicions: - reasons = set() for suspicion in suspicions: try: reason_id = int(suspicion) @@ -862,80 +845,75 @@ def add_feature_v1(request): has_visible_features = True except (ValueError, SuspicionReasons.DoesNotExist): - reason, created = SuspicionReasons.objects.get_or_create( - name=suspicion - ) + reason, created = SuspicionReasons.objects.get_or_create(name=suspicion) reasons.add(reason) if reason.is_visible: has_visible_features = True - challenges = ChallengeIntegration.objects.filter( - active=True - ).filter(reasons__in=reasons).distinct() + challenges = ( + ChallengeIntegration.objects.filter(active=True) + .filter(reasons__in=reasons) + .distinct() + ) for challenge in challenges: push_feature_to_maproulette( { "type": "Feature", - "geometry": request.data.get('geometry'), - "properties": request.data.get('properties') + "geometry": request.data.get("geometry"), + "properties": request.data.get("properties"), }, challenge.challenge_id, - feature.get('osm_id'), - [r.name for r in reasons] - ) + feature.get("osm_id"), + [r.name for r in reasons], + ) - changeset_defaults = { - 'is_suspect': has_visible_features - } + changeset_defaults = {"is_suspect": has_visible_features} changeset, created = Changeset.objects.get_or_create( - id=feature.get('changeset'), - defaults=changeset_defaults - ) + id=feature.get("changeset"), defaults=changeset_defaults + ) if type(changeset.new_features) is not list: changeset.new_features = [] elif len(changeset.new_features) > 0: for i, f in enumerate(changeset.new_features): - if f['url'] == '{}-{}'.format(feature['osm_type'], feature['osm_id']): - f['reasons'] = list(set(f['reasons'] + [i.id for i in reasons])) + if f["url"] == f"{feature['osm_type']}-{feature['osm_id']}": + f["reasons"] = list(set(f["reasons"] + [i.id for i in reasons])) changeset.save(update_fields=changeset_fields_to_update) add_reasons_to_changeset(changeset, reasons) return Response( - {'detail': 'Feature added to changeset.'}, - status=status.HTTP_201_CREATED - ) + {"detail": "Feature added to changeset."}, + status=status.HTTP_201_CREATED, + ) fields_to_save = [ - 'osm_id', 'version', 'reasons', 'name', 'note', 'primary_tags', 'url' - ] - feature['url'] = '{}-{}'.format( - feature.get('osm_type'), - feature.get('osm_id') - ) - feature['reasons'] = [i.id for i in reasons] + "osm_id", + "version", + "reasons", + "name", + "note", + "primary_tags", + "url", + ] + feature["url"] = f"{feature.get('osm_type')}-{feature.get('osm_id')}" + feature["reasons"] = [i.id for i in reasons] primary_tags = filter_primary_tags(feature) if len(primary_tags.items()): - feature['primary_tags'] = primary_tags + feature["primary_tags"] = primary_tags [feature.pop(k) for k in list(feature.keys()) if k not in fields_to_save] changeset.new_features.append(feature) if not changeset.is_suspect and has_visible_features: changeset.is_suspect = True - changeset_fields_to_update.append('is_suspect') + changeset_fields_to_update.append("is_suspect") changeset.save(update_fields=changeset_fields_to_update) - print( - 'Changeset {} {}'.format( - changeset.id, 'created' if created else 'updated' - ) - ) + print(f"Changeset {changeset.id} {'created' if created else 'updated'}") add_reasons_to_changeset(changeset, reasons) return Response( - {'detail': 'Feature added to changeset.'}, - status=status.HTTP_201_CREATED - ) + {"detail": "Feature added to changeset."}, status=status.HTTP_201_CREATED + ) def add_reasons_to_changeset(changeset, reasons): @@ -947,6 +925,6 @@ def add_reasons_to_changeset(changeset, reasons): # In this case, we can safely ignore this attempted DB Insert, # since what we wanted inserted has already been done through # a separate web request. - print('IntegrityError with changeset %s' % changeset.id) + print(f"IntegrityError with changeset {changeset.id}") except ValueError: - print('ValueError with changeset %s' % changeset.id) + print(f"ValueError with changeset {changeset.id}") diff --git a/osmchadjango/feature/apps.py b/osmchadjango/feature/apps.py index cba45383..346e144e 100644 --- a/osmchadjango/feature/apps.py +++ b/osmchadjango/feature/apps.py @@ -2,5 +2,5 @@ class FeatureConfig(AppConfig): - default_auto_field = 'django.db.models.BigAutoField' - name = 'osmchadjango.feature' + default_auto_field = "django.db.models.BigAutoField" + name = "osmchadjango.feature" diff --git a/osmchadjango/feature/filters.py b/osmchadjango/feature/filters.py index 497a2397..61017dfb 100644 --- a/osmchadjango/feature/filters.py +++ b/osmchadjango/feature/filters.py @@ -10,200 +10,209 @@ class FeatureFilter(GeoFilterSet): """Filter Feature model objects.""" + geometry = GeometryFilter( - name='geometry', - lookup_expr='intersects', + name="geometry", + lookup_expr="intersects", help_text="""Geospatial filter of features whose geometry intersects with - another geometry. You can use any geometry type in this filter.""" - ) + another geometry. You can use any geometry type in this filter.""", + ) checked = filters.BooleanFilter( widget=BooleanWidget(), help_text="""Filter features that were checked or not. Use true/false or - 1/0 values.""" - ) + 1/0 values.""", + ) changeset_checked = filters.BooleanFilter( - name='changeset__checked', + name="changeset__checked", widget=BooleanWidget(), help_text="""Filter features whose changeset is checked or not. Use - true/false or 1/0 values.""" - ) + true/false or 1/0 values.""", + ) harmful = filters.BooleanFilter( widget=BooleanWidget(), help_text="""Filter features that were marked as harmful or not harmful. - Use true/false or 1/0 values.""" - ) + Use true/false or 1/0 values.""", + ) users = filters.CharFilter( - name='changeset__user', - method='filter_changeset_users', + name="changeset__user", + method="filter_changeset_users", help_text="""Filter features whose last edit was made by a user. Use - commas to search for more than one username.""" - ) + commas to search for more than one username.""", + ) uids = filters.CharFilter( - name='changeset__uid', - method='filter_changeset_uid', + name="changeset__uid", + method="filter_changeset_uid", help_text="""Filter features whose last edit was made by a user. Use commas to search for more than one user uid. The uid is a unique - identifier of a OSM user.""" - ) + identifier of a OSM user.""", + ) checked_by = filters.CharFilter( - name='check_user', - method='filter_check_users', + name="check_user", + method="filter_check_users", help_text="""Filter features that were checked by a user. Use commas to - search for more than one user.""" - ) + search for more than one user.""", + ) order_by = filters.CharFilter( name=None, - method='order_queryset', + method="order_queryset", help_text="""Order the Features by one of the following fields: id, osm_id, changeset__date, changeset_id, check_date or number_reasons. Use a minus sign (-) before the field name to reverse the ordering. - Default ordering is '-changeset_id'.""" - ) + Default ordering is '-changeset_id'.""", + ) changeset_ids = filters.CharFilter( - name='changeset__id', - method='filter_changeset_ids', + name="changeset__id", + method="filter_changeset_ids", help_text="""Filter features by its changeset id. Send the ids separated - by commas.""" - ) + by commas.""", + ) osm_version__gte = filters.NumberFilter( - name='osm_version', - lookup_expr='gte', + name="osm_version", + lookup_expr="gte", help_text="""Filter items whose osm_version is greater than or equal to - a number.""" - ) + a number.""", + ) osm_version__lte = filters.NumberFilter( - name='osm_version', - lookup_expr='lte', + name="osm_version", + lookup_expr="lte", help_text="""Filter items whose osm_version is lower than or equal to a - number.""" - ) + number.""", + ) osm_type = filters.CharFilter( - name='osm_type', - lookup_expr='exact', + name="osm_type", + lookup_expr="exact", help_text="""Filter features by its osm_type. The value options are node, - way or relation.""" - ) + way or relation.""", + ) date__gte = filters.DateTimeFilter( - name='changeset__date', - lookup_expr='gte', + name="changeset__date", + lookup_expr="gte", help_text="""Filter features whose changeset date is greater than or - equal to a date or a datetime.""" - ) + equal to a date or a datetime.""", + ) date__lte = filters.DateTimeFilter( - name='changeset__date', - lookup_expr='lte', + name="changeset__date", + lookup_expr="lte", help_text="""Filter features whose changeset date is lower than or equal - to a date or a datetime.""" - ) + to a date or a datetime.""", + ) check_date__gte = filters.DateTimeFilter( - name='check_date', - lookup_expr='gte', + name="check_date", + lookup_expr="gte", help_text="""Filter features whose check_date is greater than or equal - to a date or a datetime.""" - ) + to a date or a datetime.""", + ) check_date__lte = filters.DateTimeFilter( - name='check_date', - lookup_expr='lte', + name="check_date", + lookup_expr="lte", help_text="""Filter features whose check_date is lower than or equal to - a date or a datetime.""" - ) + a date or a datetime.""", + ) editor = filters.CharFilter( - name='changeset__editor', - lookup_expr='icontains', + name="changeset__editor", + lookup_expr="icontains", help_text="""Filter features that were created or last modified with a software editor. The lookup expression used is 'icontains', so a query for 'josm' will get features created or last modified with all JOSM versions. - """ - ) + """, + ) reasons = filters.CharFilter( - name='reasons', - method='filter_any_reasons', + name="reasons", + method="filter_any_reasons", help_text="""Filter features that have one or more of the Suspicion - Reasons. Inform the Suspicion Reasons ids separated by commas.""" - ) + Reasons. Inform the Suspicion Reasons ids separated by commas.""", + ) all_reasons = filters.CharFilter( - name='reasons', - method='filter_all_reasons', + name="reasons", + method="filter_all_reasons", help_text="""Filter features that have ALL the Suspicion Reasons of a - list. Inform the Suspicion Reasons ids separated by commas.""" - ) + list. Inform the Suspicion Reasons ids separated by commas.""", + ) number_reasons__gte = filters.NumberFilter( - name='number_reasons', - method='filter_number_reasons', + name="number_reasons", + method="filter_number_reasons", help_text="""Filter features whose number of Suspicion Reasons is - equal or greater than a value.""" - ) + equal or greater than a value.""", + ) tags = filters.CharFilter( - name='tags', - method='filter_any_reasons', + name="tags", + method="filter_any_reasons", help_text="""Filter features that have one or more of the Tags. Inform - the Tags ids separated by commas.""" - ) + the Tags ids separated by commas.""", + ) all_tags = filters.CharFilter( - name='tags', - method='filter_all_reasons', + name="tags", + method="filter_all_reasons", help_text="""Filter features that have ALL the Tags of a list. Inform - the Tags ids separated by commas.""" - ) + the Tags ids separated by commas.""", + ) def filter_changeset_users(self, queryset, name, value): if (self.request is None or self.request.user.is_authenticated) and value: - lookup = '__'.join([name, 'in']) - users_array = [t.strip() for t in value.split(',')] + lookup = "__".join([name, "in"]) + users_array = [t.strip() for t in value.split(",")] return queryset.filter(**{lookup: users_array}) else: return queryset def filter_changeset_uid(self, queryset, name, value): if (self.request is None or self.request.user.is_authenticated) and value: - lookup = '__'.join([name, 'in']) - uids_array = [t.strip() for t in value.split(',')] + lookup = "__".join([name, "in"]) + uids_array = [t.strip() for t in value.split(",")] return queryset.filter(**{lookup: uids_array}) else: return queryset def filter_check_users(self, queryset, name, value): if (self.request is None or self.request.user.is_authenticated) and value: - lookup = '__'.join([name, 'name', 'in']) - check_users_array = [t.strip() for t in value.split(',')] + lookup = "__".join([name, "name", "in"]) + check_users_array = [t.strip() for t in value.split(",")] return queryset.filter(**{lookup: check_users_array}) else: return queryset def filter_any_reasons(self, queryset, name, value): - lookup = '__'.join([name, 'id', 'in']) - values = [int(t) for t in value.split(',')] + lookup = "__".join([name, "id", "in"]) + values = [int(t) for t in value.split(",")] return queryset.filter(**{lookup: values}).distinct() def filter_all_reasons(self, queryset, name, value): - lookup = '__'.join([name, 'id']) - values = [int(t) for t in value.split(',')] + lookup = "__".join([name, "id"]) + values = [int(t) for t in value.split(",")] for term in values: queryset = queryset.filter(**{lookup: term}) return queryset def filter_number_reasons(self, queryset, name, value): - lookup = '__'.join([name, 'gte']) - queryset = queryset.annotate(number_reasons=Count('reasons')) + lookup = "__".join([name, "gte"]) + queryset = queryset.annotate(number_reasons=Count("reasons")) return queryset.filter(**{lookup: value}) def order_queryset(self, queryset, name, value): allowed_fields = [ - '-id', 'id', '-osm_id', 'osm_id', 'changeset__date', - '-changeset__date', 'changeset_id', 'check_date', '-check_date', - 'number_reasons', '-number_reasons' - ] + "-id", + "id", + "-osm_id", + "osm_id", + "changeset__date", + "-changeset__date", + "changeset_id", + "check_date", + "-check_date", + "number_reasons", + "-number_reasons", + ] if value in allowed_fields: - if value in ['number_reasons', '-number_reasons']: - queryset = queryset.annotate(number_reasons=Count('reasons')) + if value in ["number_reasons", "-number_reasons"]: + queryset = queryset.annotate(number_reasons=Count("reasons")) return queryset.order_by(value) else: return queryset def filter_changeset_ids(self, queryset, name, value): - lookup = '__'.join([name, 'in']) - values = [int(t) for t in value.split(',')] + lookup = "__".join([name, "in"]) + values = [int(t) for t in value.split(",")] return queryset.filter(**{lookup: values}) class Meta: diff --git a/osmchadjango/feature/models.py b/osmchadjango/feature/models.py index e71a7802..fe42ceff 100644 --- a/osmchadjango/feature/models.py +++ b/osmchadjango/feature/models.py @@ -1,14 +1,13 @@ from django.contrib.gis.db import models -from django.utils.translation import ugettext, ugettext_lazy as _ from django.contrib.postgres.fields import JSONField -from ..users.models import User +from osmchadjango.users.models import User class Feature(models.Model): changeset = models.ForeignKey( - 'changeset.Changeset', on_delete=models.CASCADE, related_name='features' - ) + "changeset.Changeset", on_delete=models.CASCADE, related_name="features" + ) osm_id = models.BigIntegerField() osm_type = models.CharField(max_length=1000) osm_version = models.IntegerField() @@ -17,34 +16,38 @@ class Feature(models.Model): geojson = JSONField() old_geojson = JSONField(null=True, blank=True) reasons = models.ManyToManyField( - 'changeset.SuspicionReasons', related_name='features') - tags = models.ManyToManyField( - 'changeset.Tag', related_name='features') + "changeset.SuspicionReasons", related_name="features" + ) + tags = models.ManyToManyField("changeset.Tag", related_name="features") harmful = models.NullBooleanField(db_index=True) checked = models.BooleanField(default=False, db_index=True) check_user = models.ForeignKey( User, on_delete=models.SET_NULL, null=True, blank=True, db_index=True - ) + ) check_date = models.DateTimeField(null=True, blank=True, db_index=True) url = models.SlugField(max_length=1000, db_index=True) comparator_version = models.CharField(max_length=1000, blank=True, null=True) class Meta: - unique_together = ('changeset', 'osm_id', 'osm_type',) - ordering = ['-changeset_id'] + unique_together = ( + "changeset", + "osm_id", + "osm_type", + ) + ordering = ["-changeset_id"] def __str__(self): - return '{} {} v{}'.format(self.osm_type, self.osm_id, self.osm_version) + return f"{self.osm_type} {self.osm_id} v{self.osm_version}" def osm_link(self): """Return the link to the feature page on OSM website.""" - return 'https://www.openstreetmap.org/%s/%s' % (self.osm_type, self.osm_id) + return f"https://www.openstreetmap.org/{self.osm_type}/{self.osm_id}" @property def all_tags(self): geojson = self.geojson tags = [] - for key, value in geojson['properties'].items(): + for key, value in geojson["properties"].items(): record = {} record["tag"] = key record["Value"] = value @@ -60,18 +63,18 @@ def diff_tags(self): added_tags = [] unmodified_tags = [] tags = {} - if old_geojson and old_geojson['properties']: - old_props = old_geojson['properties'] + if old_geojson and old_geojson["properties"]: + old_props = old_geojson["properties"] else: old_props = {} for key, value in old_props.items(): - if 'osm:' not in key and 'result:' not in key: - if key in geojson['properties']: + if "osm:" not in key and "result:" not in key: + if key in geojson["properties"]: record = {} record["tag"] = key record["oldValue"] = value - record["newValue"] = geojson['properties'][key] - if value != geojson['properties'][key]: + record["newValue"] = geojson["properties"][key] + if value != geojson["properties"][key]: modified_tags.append(record) else: unmodified_tags.append(record) @@ -81,8 +84,8 @@ def diff_tags(self): record["Value"] = value deleted_tags.append(record) - for key, value in geojson['properties'].items(): - if 'osm:' not in key and 'result:' not in key: + for key, value in geojson["properties"].items(): + if "osm:" not in key and "result:" not in key: if key not in old_props: record = {} record["tag"] = key diff --git a/osmchadjango/feature/serializers.py b/osmchadjango/feature/serializers.py index b7e8f96f..0677fb05 100644 --- a/osmchadjango/feature/serializers.py +++ b/osmchadjango/feature/serializers.py @@ -1,32 +1,31 @@ from rest_framework.fields import ReadOnlyField, SerializerMethodField -from rest_framework.serializers import ( - ModelSerializer, PrimaryKeyRelatedField - ) +from rest_framework.serializers import ModelSerializer, PrimaryKeyRelatedField from rest_framework_gis.serializers import GeoFeatureModelSerializer -from ..changeset.models import Tag -from ..changeset.serializers import ( - BasicTagSerializer, BasicSuspicionReasonsSerializer - ) +from osmchadjango.changeset.models import Tag +from osmchadjango.changeset.serializers import ( + BasicTagSerializer, + BasicSuspicionReasonsSerializer, +) from .models import Feature class FeatureSerializerToStaff(GeoFeatureModelSerializer): - check_user = ReadOnlyField(source='check_user.name', default=None) - changeset = ReadOnlyField(source='changeset.id') - date = ReadOnlyField(source='changeset.date') - source = ReadOnlyField(source='changeset.source') - imagery_used = ReadOnlyField(source='changeset.imagery_used') - editor = ReadOnlyField(source='changeset.editor') - comment = ReadOnlyField(source='changeset.comment') + check_user = ReadOnlyField(source="check_user.name", default=None) + changeset = ReadOnlyField(source="changeset.id") + date = ReadOnlyField(source="changeset.date") + source = ReadOnlyField(source="changeset.source") + imagery_used = ReadOnlyField(source="changeset.imagery_used") + editor = ReadOnlyField(source="changeset.editor") + comment = ReadOnlyField(source="changeset.comment") reasons = BasicSuspicionReasonsSerializer(many=True, read_only=True) tags = BasicTagSerializer(many=True, read_only=True) osm_link = SerializerMethodField() class Meta: model = Feature - geo_field = 'geometry' - exclude = ('comparator_version',) + geo_field = "geometry" + exclude = ("comparator_version",) def get_osm_link(self, obj): return obj.osm_link() @@ -38,17 +37,13 @@ class FeatureSerializer(FeatureSerializerToStaff): def get_reasons(self, obj): return BasicSuspicionReasonsSerializer( - obj.reasons.filter(is_visible=True), - many=True, - read_only=True - ).data + obj.reasons.filter(is_visible=True), many=True, read_only=True + ).data def get_tags(self, obj): return BasicTagSerializer( - obj.tags.filter(is_visible=True), - many=True, - read_only=True - ).data + obj.tags.filter(is_visible=True), many=True, read_only=True + ).data class FeatureSerializerToUnauthenticated(FeatureSerializer): @@ -56,8 +51,8 @@ class FeatureSerializerToUnauthenticated(FeatureSerializer): class Meta: model = Feature - geo_field = 'geometry' - exclude = ('comparator_version', 'check_user') + geo_field = "geometry" + exclude = ("comparator_version", "check_user") class FeatureTagsSerializer(ModelSerializer): @@ -65,4 +60,4 @@ class FeatureTagsSerializer(ModelSerializer): class Meta: model = Feature - fields = ('tags',) + fields = ("tags",) diff --git a/osmchadjango/feature/urls.py b/osmchadjango/feature/urls.py index a145c547..88ed22aa 100644 --- a/osmchadjango/feature/urls.py +++ b/osmchadjango/feature/urls.py @@ -1,48 +1,41 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import, unicode_literals - -from django.conf.urls import re_path +from django.urls import path from . import views -app_name = 'feature' +app_name = "feature" urlpatterns = [ - re_path( - r'^features/$', - view=views.FeatureListAPIView.as_view(), - name='list' - ), - re_path( - r'^features/(?P\d+)-(?P[a-zA-Z0-9-]+)/$', + path("features/", view=views.FeatureListAPIView.as_view(), name="list"), + path( + "features/-/", view=views.FeatureDetailAPIView.as_view(), - name='detail' + name="detail", ), - # re_path( - # r'^features/create/$', + # path( + # 'features/create/', # view=views.create_feature, # name='create' # ), - re_path( - r'^features/(?P\d+)-(?P[a-zA-Z0-9-]+)/set-harmful/$', - view=views.CheckFeature.as_view({'put': 'set_harmful'}), - name='set-harmful' + path( + "features/-/set-harmful/", + view=views.CheckFeature.as_view({"put": "set_harmful"}), + name="set-harmful", ), - re_path( - r'^features/(?P\d+)-(?P[a-zA-Z0-9-]+)/set-good/$', - view=views.CheckFeature.as_view({'put': 'set_good'}), - name='set-good' + path( + "features/-/set-good/", + view=views.CheckFeature.as_view({"put": "set_good"}), + name="set-good", ), - re_path( - r'^features/(?P\d+)-(?P[a-zA-Z0-9-]+)/uncheck/$', + path( + "features/-/uncheck/", view=views.uncheck_feature, - name='uncheck' + name="uncheck", ), - re_path( - r'^features/(?P\d+)-(?P[a-zA-Z0-9-]+)/tags/(?P\w+)/$', + path( + "features/-/tags//", view=views.AddRemoveFeatureTagsAPIView.as_view( - {'post': 'add_tag', 'delete': 'remove_tag'} - ), - name='tags' + {"post": "add_tag", "delete": "remove_tag"} + ), + name="tags", ), ] diff --git a/osmchadjango/feature/views.py b/osmchadjango/feature/views.py index 1aa3cda6..47ff87ba 100644 --- a/osmchadjango/feature/views.py +++ b/osmchadjango/feature/views.py @@ -7,12 +7,14 @@ from django.contrib.gis.gdal.error import GDALException import django_filters.rest_framework -from rest_framework.generics import ( - ListAPIView, RetrieveAPIView, get_object_or_404 - ) +from rest_framework.generics import ListAPIView, RetrieveAPIView, get_object_or_404 from rest_framework.decorators import ( - api_view, parser_classes, permission_classes, action, throttle_classes - ) + api_view, + parser_classes, + permission_classes, + action, + throttle_classes, +) from rest_framework.parsers import JSONParser, MultiPartParser, FormParser from rest_framework.permissions import IsAuthenticated, IsAdminUser from rest_framework.renderers import JSONRenderer, BrowsableAPIRenderer @@ -21,17 +23,21 @@ from rest_framework import status from rest_framework_gis.filters import InBBoxFilter -from ..changeset import models as changeset_models -from ..changeset.views import ( - StandardResultsSetPagination, PaginatedCSVRenderer, NonStaffUserThrottle - ) +from osmchadjango.changeset import models as changeset_models +from osmchadjango.changeset.views import ( + StandardResultsSetPagination, + PaginatedCSVRenderer, + NonStaffUserThrottle, +) from .models import Feature from .filters import FeatureFilter from .serializers import ( - FeatureSerializer, FeatureSerializerToStaff, FeatureTagsSerializer, - FeatureSerializerToUnauthenticated - ) + FeatureSerializer, + FeatureSerializerToStaff, + FeatureTagsSerializer, + FeatureSerializerToUnauthenticated, +) class FeatureListAPIView(ListAPIView): @@ -40,17 +46,20 @@ class FeatureListAPIView(ListAPIView): the min Lat, min Lon, max Lat, max Lon values. It's also possible to filter using other fields. The default pagination returns 50 objects by page. """ - queryset = Feature.objects.all().select_related( - 'check_user', 'changeset' - ).prefetch_related('reasons', 'tags') + + queryset = ( + Feature.objects.all() + .select_related("check_user", "changeset") + .prefetch_related("reasons", "tags") + ) serializer_class = FeatureSerializer pagination_class = StandardResultsSetPagination renderer_classes = (JSONRenderer, BrowsableAPIRenderer, PaginatedCSVRenderer) - bbox_filter_field = 'geometry' + bbox_filter_field = "geometry" filter_backends = ( InBBoxFilter, django_filters.rest_framework.DjangoFilterBackend, - ) + ) bbox_filter_include_overlapping = True filter_class = FeatureFilter @@ -64,14 +73,17 @@ def get_serializer_class(self): class FeatureDetailAPIView(RetrieveAPIView): - '''Get details of a Feature object. Type: GeoJSON''' - queryset = Feature.objects.all().select_related( - 'check_user', 'changeset' - ).prefetch_related('reasons', 'tags') + """Get details of a Feature object. Type: GeoJSON""" + + queryset = ( + Feature.objects.all() + .select_related("check_user", "changeset") + .prefetch_related("reasons", "tags") + ) def get_object(self): - changeset = self.kwargs['changeset'] - url = self.kwargs['slug'] + changeset = self.kwargs["changeset"] + url = self.kwargs["slug"] return get_object_or_404(Feature, changeset=changeset, url=url) def get_serializer_class(self): @@ -83,101 +95,89 @@ def get_serializer_class(self): return FeatureSerializerToUnauthenticated -@api_view(['POST']) +@api_view(["POST"]) @throttle_classes((NonStaffUserThrottle,)) @parser_classes((JSONParser, MultiPartParser, FormParser)) @permission_classes((IsAuthenticated, IsAdminUser)) def create_feature(request): - '''Create Suspicion Features. It was designed to receive vandalism-dynamosm + """Create Suspicion Features. It was designed to receive vandalism-dynamosm json output. Only staff users have permissions to create features. You can use the django admin to get a Token to the user you want to use to created features. - ''' + """ feature = request.data - if 'properties' not in feature.keys(): + if "properties" not in feature.keys(): return Response( - {'detail': 'Expecting a single GeoJSON feature.'}, - status=status.HTTP_400_BAD_REQUEST - ) - properties = feature.get('properties', {}) - changeset_id = properties.get('osm:changeset') + {"detail": "Expecting a single GeoJSON feature."}, + status=status.HTTP_400_BAD_REQUEST, + ) + properties = feature.get("properties", {}) + changeset_id = properties.get("osm:changeset") if not changeset_id: return Response( - {'detail': 'osm:changeset field is missing.'}, - status=status.HTTP_400_BAD_REQUEST - ) + {"detail": "osm:changeset field is missing."}, + status=status.HTTP_400_BAD_REQUEST, + ) defaults = { - 'osm_id': properties['osm:id'], - 'osm_type': properties['osm:type'], - 'url': '{}-{}' .format(properties['osm:type'], properties['osm:id']), - 'osm_version': properties['osm:version'], - 'comparator_version': feature.get('comparator_version'), - } + "osm_id": properties["osm:id"], + "osm_type": properties["osm:type"], + "url": f"{properties['osm:type']}-{properties['osm:id']}", + "osm_version": properties["osm:version"], + "comparator_version": feature.get("comparator_version"), + } try: - defaults['geometry'] = GEOSGeometry(json.dumps(feature['geometry'])) + defaults["geometry"] = GEOSGeometry(json.dumps(feature["geometry"])) except (GDALException, ValueError, TypeError) as e: return Response( - {'detail': '{} in geometry field of feature {}'.format(e, properties['osm:id'])}, - status=status.HTTP_400_BAD_REQUEST - ) + {"detail": f"{e} in geometry field of feature {properties['osm:id']}"}, + status=status.HTTP_400_BAD_REQUEST, + ) - if 'oldVersion' in properties.keys(): + if "oldVersion" in properties.keys(): try: - defaults['old_geometry'] = GEOSGeometry( - json.dumps(properties['oldVersion']['geometry']) - ) + defaults["old_geometry"] = GEOSGeometry( + json.dumps(properties["oldVersion"]["geometry"]) + ) except (GDALException, ValueError, TypeError, KeyError) as e: - print( - '{} in oldVersion.geometry field of feature {}'.format( - e, properties['osm:id'] - ) - ) - defaults['old_geojson'] = feature['properties'].pop('oldVersion') + print(f"{e} in oldVersion.geometry field of feature {properties['osm:id']}") + defaults["old_geojson"] = feature["properties"].pop("oldVersion") # Each changed feature should have a 'suspicions' array of objects in its properties - print( - 'Creating feature {} of changeset {}.'.format( - properties['osm:id'], changeset_id - ) - ) - suspicions = feature['properties'].pop('suspicions') + print(f"Creating feature {properties['osm:id']} of changeset {changeset_id}.") + suspicions = feature["properties"].pop("suspicions") has_visible_features = False + reasons = set() if suspicions: - reasons = set() for suspicion in suspicions: - if suspicion.get('is_visible') is False: + if suspicion.get("is_visible") is False: is_visible = False else: has_visible_features = True is_visible = True reason, created = changeset_models.SuspicionReasons.objects.get_or_create( - name=suspicion['reason'], - defaults={'is_visible': is_visible} - ) + name=suspicion["reason"], defaults={"is_visible": is_visible} + ) reasons.add(reason) changeset_defaults = { - 'date': datetime.utcfromtimestamp(properties.get('osm:timestamp') / 1000), - 'uid': properties.get('osm:uid'), - 'is_suspect': has_visible_features - } + "date": datetime.utcfromtimestamp(properties.get("osm:timestamp") / 1000), + "uid": properties.get("osm:uid"), + "is_suspect": has_visible_features, + } changeset, created = changeset_models.Changeset.objects.get_or_create( - id=changeset_id, - defaults=changeset_defaults - ) + id=changeset_id, defaults=changeset_defaults + ) if not changeset.is_suspect and has_visible_features: changeset.is_suspect = True - changeset.save(update_fields=['is_suspect']) + changeset.save(update_fields=["is_suspect"]) - print( - 'Changeset {} {}'.format(changeset_id, 'created' if created else 'updated') - ) + print(f"Changeset {changeset_id} {'created' if created else 'updated'}") try: changeset.reasons.add(*reasons) @@ -187,34 +187,25 @@ def create_feature(request): # In this case, we can safely ignore this attempted DB Insert, # since what we wanted inserted has already been done through # a separate web request. - print('IntegrityError with changeset %s' % changeset_id) + print(f"IntegrityError with changeset {changeset_id}") except ValueError as e: - print('ValueError with changeset %s' % changeset_id) + print(f"ValueError with changeset {changeset_id}") - defaults['geojson'] = feature + defaults["geojson"] = feature suspicious_feature, created = Feature.objects.get_or_create( - osm_id=properties['osm:id'], - changeset=changeset, - defaults=defaults - ) - print( - 'Feature {} {}.'.format( - properties['osm:id'], 'created' if created else 'updated' - ) - ) + osm_id=properties["osm:id"], changeset=changeset, defaults=defaults + ) + print(f"Feature {properties['osm:id']} {'created' if created else 'updated'}.") try: suspicious_feature.reasons.add(*reasons) except IntegrityError: # This most often happens due to duplicates in dynamosm stream - print('Integrity error with feature %s' % suspicious_feature.osm_id) + print(f"Integrity error with feature {suspicious_feature.osm_id}") except ValueError as e: - print('Value error with feature %s' % suspicious_feature.osm_id) + print(f"Value error with feature {suspicious_feature.osm_id}") - return Response( - {'detail': 'Feature created.'}, - status=status.HTTP_201_CREATED - ) + return Response({"detail": "Feature created."}, status=status.HTTP_201_CREATED) class CheckFeature(ModelViewSet): @@ -224,20 +215,18 @@ class CheckFeature(ModelViewSet): throttle_classes = (NonStaffUserThrottle,) def update_feature(self, feature, request, harmful): - """Update the feature fields and return a 200 Response """ + """Update the feature fields and return a 200 Response""" feature.checked = True feature.harmful = harmful feature.check_user = request.user feature.check_date = timezone.now() - feature.save( - update_fields=['checked', 'harmful', 'check_user', 'check_date'] - ) + feature.save(update_fields=["checked", "harmful", "check_user", "check_date"]) return Response( - {'detail': 'Feature marked as {}.'.format('harmful' if harmful else 'good')}, - status=status.HTTP_200_OK - ) + {"detail": f"Feature marked as {'harmful' if harmful else 'good'}."}, + status=status.HTTP_200_OK, + ) - @action(detail=True, methods=['put']) + @action(detail=True, methods=["put"]) def set_harmful(self, request, changeset, slug): """Mark a feature as harmful. You can set the tags of the feature by sending a list of tag ids inside a field named 'tags' in the request data. If @@ -247,26 +236,25 @@ def set_harmful(self, request, changeset, slug): feature = get_object_or_404(Feature, changeset=changeset, url=slug) if feature.checked: return Response( - {'detail': 'Feature was already checked.'}, - status=status.HTTP_403_FORBIDDEN - ) - if feature.changeset.uid in request.user.social_auth.values_list('uid', flat=True): + {"detail": "Feature was already checked."}, + status=status.HTTP_403_FORBIDDEN, + ) + if feature.changeset.uid in request.user.social_auth.values_list( + "uid", flat=True + ): return Response( - {'detail': 'User can not check his own feature.'}, - status=status.HTTP_403_FORBIDDEN - ) + {"detail": "User can not check his own feature."}, + status=status.HTTP_403_FORBIDDEN, + ) if request.data: serializer = FeatureTagsSerializer(data=request.data) if serializer.is_valid(): - feature.tags.set(serializer.data['tags']) + feature.tags.set(serializer.data["tags"]) else: - return Response( - serializer.errors, - status=status.HTTP_400_BAD_REQUEST - ) + return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) return self.update_feature(feature, request, harmful=True) - @action(detail=True, methods=['put']) + @action(detail=True, methods=["put"]) def set_good(self, request, changeset, slug): """Mark a feature as good. You can set the tags of the feature by sending a list of tag ids inside a field named 'tags' in the request data. If @@ -276,27 +264,26 @@ def set_good(self, request, changeset, slug): feature = get_object_or_404(Feature, changeset=changeset, url=slug) if feature.checked: return Response( - {'detail': 'Feature was already checked.'}, - status=status.HTTP_403_FORBIDDEN - ) - if feature.changeset.uid in request.user.social_auth.values_list('uid', flat=True): + {"detail": "Feature was already checked."}, + status=status.HTTP_403_FORBIDDEN, + ) + if feature.changeset.uid in request.user.social_auth.values_list( + "uid", flat=True + ): return Response( - {'detail': 'User can not check his own feature.'}, - status=status.HTTP_403_FORBIDDEN - ) + {"detail": "User can not check his own feature."}, + status=status.HTTP_403_FORBIDDEN, + ) if request.data: serializer = FeatureTagsSerializer(data=request.data) if serializer.is_valid(): - feature.tags.set(serializer.data['tags']) + feature.tags.set(serializer.data["tags"]) else: - return Response( - serializer.errors, - status=status.HTTP_400_BAD_REQUEST - ) + return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) return self.update_feature(feature, request, harmful=False) -@api_view(['PUT']) +@api_view(["PUT"]) @parser_classes((JSONParser, MultiPartParser, FormParser)) @permission_classes((IsAuthenticated,)) def uncheck_feature(request, changeset, slug): @@ -306,26 +293,22 @@ def uncheck_feature(request, changeset, slug): instance = get_object_or_404(Feature, changeset=changeset, url=slug) if instance.checked is False: return Response( - {'detail': 'Feature is not checked.'}, - status=status.HTTP_403_FORBIDDEN - ) + {"detail": "Feature is not checked."}, status=status.HTTP_403_FORBIDDEN + ) elif request.user == instance.check_user or request.user.is_staff: instance.checked = False instance.harmful = None instance.check_user = None instance.check_date = None - instance.save( - update_fields=['checked', 'harmful', 'check_user', 'check_date'] - ) + instance.save(update_fields=["checked", "harmful", "check_user", "check_date"]) return Response( - {'detail': 'Feature marked as unchecked.'}, - status=status.HTTP_200_OK - ) + {"detail": "Feature marked as unchecked."}, status=status.HTTP_200_OK + ) else: return Response( - {'detail': 'User does not have permission to uncheck this feature.'}, - status=status.HTTP_403_FORBIDDEN - ) + {"detail": "User does not have permission to uncheck this feature."}, + status=status.HTTP_403_FORBIDDEN, + ) class AddRemoveFeatureTagsAPIView(ModelViewSet): @@ -335,7 +318,7 @@ class AddRemoveFeatureTagsAPIView(ModelViewSet): # docs schema generation. serializer_class = FeatureTagsSerializer - @action(detail=True, methods=['post']) + @action(detail=True, methods=["post"]) def add_tag(self, request, changeset, slug, tag_pk): """Add a tag to a feature. If the feature is unchecked, any user can add and remove tags. After the feature got checked, only staff users @@ -343,34 +326,35 @@ def add_tag(self, request, changeset, slug, tag_pk): created the feature can't add or remove tags. """ feature = get_object_or_404( - Feature.objects.all(), - changeset=changeset, - url=slug - ) + Feature.objects.all(), changeset=changeset, url=slug + ) tag = get_object_or_404( - changeset_models.Tag.objects.filter(for_feature=True), - pk=tag_pk - ) + changeset_models.Tag.objects.filter(for_feature=True), pk=tag_pk + ) - if feature.changeset.uid in request.user.social_auth.values_list('uid', flat=True): + if feature.changeset.uid in request.user.social_auth.values_list( + "uid", flat=True + ): return Response( - {'detail': 'User can not add tags to his own feature.'}, - status=status.HTTP_403_FORBIDDEN - ) + {"detail": "User can not add tags to his own feature."}, + status=status.HTTP_403_FORBIDDEN, + ) if feature.checked and ( - request.user != feature.check_user and not request.user.is_staff): + request.user != feature.check_user and not request.user.is_staff + ): return Response( - {'detail': 'User can not add tags to a feature checked by another user.'}, - status=status.HTTP_403_FORBIDDEN - ) + { + "detail": "User can not add tags to a feature checked by another user." + }, + status=status.HTTP_403_FORBIDDEN, + ) feature.tags.add(tag) return Response( - {'detail': 'Tag added to the feature.'}, - status=status.HTTP_200_OK - ) + {"detail": "Tag added to the feature."}, status=status.HTTP_200_OK + ) - @action(detail=True, methods=['delete']) + @action(detail=True, methods=["delete"]) def remove_tag(self, request, changeset, slug, tag_pk): """Remove a tag from a feature. If the feature is unchecked, any user can add and remove tags. After the feature got checked, only staff users @@ -378,26 +362,28 @@ def remove_tag(self, request, changeset, slug, tag_pk): created the feature can't add or remove tags. """ feature = get_object_or_404( - Feature.objects.all(), - changeset=changeset, - url=slug - ) + Feature.objects.all(), changeset=changeset, url=slug + ) tag = get_object_or_404(changeset_models.Tag.objects.all(), pk=tag_pk) - if feature.changeset.uid in request.user.social_auth.values_list('uid', flat=True): + if feature.changeset.uid in request.user.social_auth.values_list( + "uid", flat=True + ): return Response( - {'detail': 'User can not remove tags from his own feature.'}, - status=status.HTTP_403_FORBIDDEN - ) + {"detail": "User can not remove tags from his own feature."}, + status=status.HTTP_403_FORBIDDEN, + ) if feature.checked and ( - request.user != feature.check_user and not request.user.is_staff): + request.user != feature.check_user and not request.user.is_staff + ): return Response( - {'detail': 'User can not remove tags of a feature checked by another user.'}, - status=status.HTTP_403_FORBIDDEN - ) + { + "detail": "User can not remove tags of a feature checked by another user." + }, + status=status.HTTP_403_FORBIDDEN, + ) feature.tags.remove(tag) return Response( - {'detail': 'Tag removed from the feature.'}, - status=status.HTTP_200_OK - ) + {"detail": "Tag removed from the feature."}, status=status.HTTP_200_OK + ) diff --git a/osmchadjango/frontend/apps.py b/osmchadjango/frontend/apps.py index 4a817c7e..5b37c7ac 100644 --- a/osmchadjango/frontend/apps.py +++ b/osmchadjango/frontend/apps.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - from django.apps import AppConfig diff --git a/osmchadjango/frontend/management/commands/update_frontend.py b/osmchadjango/frontend/management/commands/update_frontend.py index 35dda4ab..56940f17 100644 --- a/osmchadjango/frontend/management/commands/update_frontend.py +++ b/osmchadjango/frontend/management/commands/update_frontend.py @@ -18,42 +18,36 @@ def handle(self, *args, **options): temp_dir = tempfile.mkdtemp() static_dir = settings.STATICFILES_DIRS[0] templates_dir = join( - settings.APPS_DIR.root, 'frontend', 'templates', 'frontend' - ) + settings.APPS_DIR.root, "frontend", "templates", "frontend" + ) repo = git.Repo.clone_from( - 'https://github.com/mapbox/osmcha-frontend.git', + "https://github.com/mapbox/osmcha-frontend.git", temp_dir, branch=settings.OSMCHA_FRONTEND_VERSION, - depth=1 - ) - print('Cloned osmcha-frontend ({}) to {}'.format(repo.commit().hexsha, temp_dir)) + depth=1, + ) + print(f"Cloned osmcha-frontend ({repo.commit().hexsha}) to {temp_dir}") main_files = [ - 'service-worker.js', 'manifest.json', 'asset-manifest.json', - 'favicon.ico' - ] + "service-worker.js", + "manifest.json", + "asset-manifest.json", + "favicon.ico", + ] for file in main_files: shutil.copyfile(join(temp_dir, file), join(static_dir, file)) static_files = [ - join('css', f) - for f in listdir(join(temp_dir, 'static', 'css')) - ] + join("css", f) for f in listdir(join(temp_dir, "static", "css")) + ] + static_files += [join("js", f) for f in listdir(join(temp_dir, "static", "js"))] static_files += [ - join('js', f) - for f in listdir(join(temp_dir, 'static', 'js')) - ] - static_files += [ - join('media', f) - for f in listdir(join(temp_dir, 'static', 'media')) - ] + join("media", f) for f in listdir(join(temp_dir, "static", "media")) + ] for file in static_files: - shutil.copyfile( - join(temp_dir, 'static', file), - join(static_dir, file) - ) + shutil.copyfile(join(temp_dir, "static", file), join(static_dir, file)) - html_files = [f for f in listdir(temp_dir) if f.endswith('.html')] + html_files = [f for f in listdir(temp_dir) if f.endswith(".html")] for file in html_files: shutil.copyfile(join(temp_dir, file), join(templates_dir, file)) diff --git a/osmchadjango/frontend/models.py b/osmchadjango/frontend/models.py index bd4b2abe..71a83623 100644 --- a/osmchadjango/frontend/models.py +++ b/osmchadjango/frontend/models.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - from django.db import models # Create your models here. diff --git a/osmchadjango/frontend/urls.py b/osmchadjango/frontend/urls.py index 74b59b78..f523ea2c 100644 --- a/osmchadjango/frontend/urls.py +++ b/osmchadjango/frontend/urls.py @@ -1,51 +1,39 @@ -from __future__ import absolute_import, unicode_literals - -from django.urls import re_path +from django.urls import path from django.views.generic.base import TemplateView, RedirectView from . import views -app_name = 'frontend' +app_name = "frontend" urlpatterns = [ - re_path( - r'^$', - view=TemplateView.as_view(template_name="frontend/index.html"), - name='index' + path( + "", view=TemplateView.as_view(template_name="frontend/index.html"), name="index" ), - re_path( - r'^index.html', + path( + "index.html", view=TemplateView.as_view(template_name="frontend/index.html"), - name='index.html' + name="index.html", ), - re_path( - r'^oauth-landing.html', + path( + "oauth-landing.html", view=TemplateView.as_view(template_name="frontend/oauth-landing.html"), - name='oauth-landing' + name="oauth-landing", ), - re_path( - r'^local-landing.html', + path( + "local-landing.html", view=TemplateView.as_view(template_name="frontend/local-landing.html"), - name='local-landing' - ), - re_path( - r'^service-worker.js', - view=views.service_worker_view, - name='service-worker' + name="local-landing", ), - re_path( - r'^favicon.ico', - view=views.favicon_view, - name='favicon' - ), - re_path( - r'^changesets/(?P\d+)/$', + path("service-worker.js", view=views.service_worker_view, name="service-worker"), + path("favicon.ico", view=views.favicon_view, name="favicon"), + path( + "changesets//", view=TemplateView.as_view(template_name="frontend/index.html"), - name='changeset-detail' + name="changeset-detail", + ), + path( + "/", + view=RedirectView.as_view(pattern_name="frontend:changeset-detail"), + name="changeset-detail-redirect", ), - re_path( - r'^(?P\d+)/$', - view=RedirectView.as_view(pattern_name='frontend:changeset-detail'), - name='changeset-detail-redirect' - ) ] diff --git a/osmchadjango/frontend/views.py b/osmchadjango/frontend/views.py index 819945de..99e992c6 100644 --- a/osmchadjango/frontend/views.py +++ b/osmchadjango/frontend/views.py @@ -1,17 +1,16 @@ -import json from os.path import join from django.conf import settings -from django.http import HttpResponse, JsonResponse +from django.http import HttpResponse def service_worker_view(request): - data = open(join(settings.STATICFILES_DIRS[0], 'service-worker.js')).read() + data = open(join(settings.STATICFILES_DIRS[0], "service-worker.js")).read() return HttpResponse(data, content_type="text/javascript") def favicon_view(request): return HttpResponse( - open(join(settings.STATICFILES_DIRS[0], 'favicon.ico'), 'rb'), - content_type='image/x-icon' - ) + open(join(settings.STATICFILES_DIRS[0], "favicon.ico"), "rb"), + content_type="image/x-icon", + ) diff --git a/osmchadjango/roulette_integration/models.py b/osmchadjango/roulette_integration/models.py index 863cd3e8..dd942810 100644 --- a/osmchadjango/roulette_integration/models.py +++ b/osmchadjango/roulette_integration/models.py @@ -1,22 +1,22 @@ from django.db import models from django.contrib.auth import get_user_model -from ..changeset.models import SuspicionReasons +from osmchadjango.changeset.models import SuspicionReasons User = get_user_model() class ChallengeIntegration(models.Model): challenge_id = models.IntegerField(unique=True, db_index=True) - reasons = models.ManyToManyField(SuspicionReasons, related_name='challenges') + reasons = models.ManyToManyField(SuspicionReasons, related_name="challenges") active = models.BooleanField(default=True, db_index=True) created = models.DateTimeField(auto_now_add=True) user = models.ForeignKey(User, related_name="challenges", on_delete=models.CASCADE) def __str__(self): - return 'Challenge {}'.format(self.challenge_id) + return f"Challenge {self.challenge_id}" class Meta: - ordering = ['id'] - verbose_name = 'Challenge Integration' - verbose_name_plural = 'Challenge Integrations' + ordering = ["id"] + verbose_name = "Challenge Integration" + verbose_name_plural = "Challenge Integrations" diff --git a/osmchadjango/roulette_integration/serializers.py b/osmchadjango/roulette_integration/serializers.py index 524b93c8..4469d81b 100644 --- a/osmchadjango/roulette_integration/serializers.py +++ b/osmchadjango/roulette_integration/serializers.py @@ -1,13 +1,12 @@ from rest_framework.fields import HiddenField, CurrentUserDefault, ReadOnlyField -from rest_framework.serializers import ModelSerializer, PrimaryKeyRelatedField +from rest_framework.serializers import ModelSerializer -from ..changeset.serializers import SuspicionReasonsSerializer from .models import ChallengeIntegration class ChallengeIntegrationSerializer(ModelSerializer): user = HiddenField(default=CurrentUserDefault()) - owner = ReadOnlyField(source='user.username', default=None) + owner = ReadOnlyField(source="user.username", default=None) # def create(self, validated_data): # import ipdb; ipdb.set_trace() @@ -16,5 +15,5 @@ class ChallengeIntegrationSerializer(ModelSerializer): class Meta: model = ChallengeIntegration - fields = ['id', 'challenge_id', 'reasons', 'user', 'active', 'created', 'owner'] - read_only_fields = ('created', 'owner') + fields = ["id", "challenge_id", "reasons", "user", "active", "created", "owner"] + read_only_fields = ("created", "owner") diff --git a/osmchadjango/roulette_integration/urls.py b/osmchadjango/roulette_integration/urls.py index b71afc8a..94516af6 100644 --- a/osmchadjango/roulette_integration/urls.py +++ b/osmchadjango/roulette_integration/urls.py @@ -1,21 +1,17 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import, unicode_literals - -from django.urls import re_path +from django.urls import path from . import views - -app_name = 'roulette_integration' +app_name = "roulette_integration" urlpatterns = [ - re_path( - r'^challenges/$', + path( + "challenges/", view=views.ChallengeIntegrationListCreateAPIView.as_view(), - name='list-create' - ), - re_path( - r'^challenges/(?P\d+)/$', + name="list-create", + ), + path( + "challenges//", view=views.ChallengeIntegrationDetailAPIView.as_view(), - name='detail' - ), - ] + name="detail", + ), +] diff --git a/osmchadjango/roulette_integration/utils.py b/osmchadjango/roulette_integration/utils.py index 34c80073..cb13ce9b 100644 --- a/osmchadjango/roulette_integration/utils.py +++ b/osmchadjango/roulette_integration/utils.py @@ -7,43 +7,42 @@ def remove_unneeded_properties(feature): keys_to_remove = [ - key for key in feature['properties'].keys() - if key.startswith('osm:') or key.startswith('result:') - ] + key + for key in feature["properties"].keys() + if key.startswith("osm:") or key.startswith("result:") + ] for key in keys_to_remove: - feature['properties'].pop(key) + feature["properties"].pop(key) - if feature['properties'].get('oldVersion'): - feature['properties'].pop('oldVersion') - if feature['properties'].get('suspicions'): - feature['properties'].pop('suspicions') + if feature["properties"].get("oldVersion"): + feature["properties"].pop("oldVersion") + if feature["properties"].get("suspicions"): + feature["properties"].pop("suspicions") return feature def format_challenge_task_payload(feature, challenge_id, name, reasons=[]): if len(reasons): - feature['properties']['osmcha_reasons'] = ", ".join([i for i in reasons]) + feature["properties"]["osmcha_reasons"] = ", ".join([i for i in reasons]) payload = { "parent": challenge_id, - "name": "{}".format(name), - "geometries": {"features": [remove_unneeded_properties(feature)]} - } + "name": f"{name}", + "geometries": {"features": [remove_unneeded_properties(feature)]}, + } return json.dumps(payload) def push_feature_to_maproulette(feature, challenge_id, name, reasons=[]): - if (settings.MAP_ROULETTE_API_KEY is not None and - settings.MAP_ROULETTE_API_URL is not None): - payload = format_challenge_task_payload( - feature, challenge_id, name, reasons - ) + if ( + settings.MAP_ROULETTE_API_KEY is not None + and settings.MAP_ROULETTE_API_URL is not None + ): + payload = format_challenge_task_payload(feature, challenge_id, name, reasons) headers = { "Content-Type": "application/json", - "apiKey": settings.MAP_ROULETTE_API_KEY - } + "apiKey": settings.MAP_ROULETTE_API_KEY, + } return requests.post( - join(settings.MAP_ROULETTE_API_URL, 'task'), - headers=headers, - data=payload - ) + join(settings.MAP_ROULETTE_API_URL, "task"), headers=headers, data=payload + ) diff --git a/osmchadjango/roulette_integration/views.py b/osmchadjango/roulette_integration/views.py index 936cd552..70df4606 100644 --- a/osmchadjango/roulette_integration/views.py +++ b/osmchadjango/roulette_integration/views.py @@ -17,11 +17,12 @@ class ChallengeIntegrationListCreateAPIView(ListCreateAPIView): It requires a challenge_id and a list of SuspicionReasons. The challenge_id must be unique. This endpoint is restricted to staff users. """ + permission_classes = (IsAdminUser,) serializer_class = ChallengeIntegrationSerializer filter_backends = (OrderingFilter,) - ordering_fields = ('created', 'challenge_id') - ordering = '-created' + ordering_fields = ("created", "challenge_id") + ordering = "-created" queryset = ChallengeIntegration.objects.all() def perform_create(self, serializer): @@ -46,6 +47,7 @@ class ChallengeIntegrationDetailAPIView(RetrieveUpdateDestroyAPIView): Delete an MapRoulette challenge. Only staff users can delete it. """ + permission_classes = (IsAdminUser,) serializer_class = ChallengeIntegrationSerializer queryset = ChallengeIntegration.objects.all() diff --git a/osmchadjango/supervise/apps.py b/osmchadjango/supervise/apps.py index 62da04fd..7bfec57f 100644 --- a/osmchadjango/supervise/apps.py +++ b/osmchadjango/supervise/apps.py @@ -4,4 +4,3 @@ class SuperviseConfig(AppConfig): name = "osmchadjango.supervise" default_auto_field = "django.db.models.BigAutoField" - diff --git a/osmchadjango/supervise/models.py b/osmchadjango/supervise/models.py index be4545e9..a885d559 100644 --- a/osmchadjango/supervise/models.py +++ b/osmchadjango/supervise/models.py @@ -7,19 +7,19 @@ from osmchadjango.changeset.filters import ChangesetFilter from osmchadjango.feature.filters import FeatureFilter -from ..users.models import User +from osmchadjango.users.models import User class AreaOfInterest(models.Model): id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) name = models.CharField(max_length=255, blank=True) - user = models.ForeignKey('users.User', on_delete=models.CASCADE) + user = models.ForeignKey("users.User", on_delete=models.CASCADE) date = models.DateTimeField(auto_now_add=True) filters = JSONField() geometry = models.GeometryField(blank=True, null=True) def __str__(self): - return '{} by {}'.format(self.name, self.user.username) + return f"{self.name} by {self.user.username}" def changesets(self, request=None): """Return the changesets that match the filters, including the geometry @@ -30,9 +30,7 @@ def changesets(self, request=None): request.user = self.user qs = ChangesetFilter(self.filters, request=request).qs if self.geometry is not None: - return qs.filter( - bbox__intersects=self.geometry - ) + return qs.filter(bbox__intersects=self.geometry) else: return qs @@ -42,17 +40,18 @@ def features(self): """ qs = FeatureFilter(self.filters).qs if self.geometry is not None: - return qs.filter( - geometry__intersects=self.geometry - ) + return qs.filter(geometry__intersects=self.geometry) else: return qs class Meta: - unique_together = ('user', 'name',) - ordering = ['-date'] - verbose_name = 'Area of Interest' - verbose_name_plural = 'Areas of Interest' + unique_together = ( + "user", + "name", + ) + ordering = ["-date"] + verbose_name = "Area of Interest" + verbose_name_plural = "Areas of Interest" class BlacklistedUser(models.Model): @@ -69,5 +68,5 @@ def save(self, *args, **kwargs): super(BlacklistedUser, self).save(*args, **kwargs) class Meta: - unique_together = ('uid', 'added_by') - ordering = ['-date'] + unique_together = ("uid", "added_by") + ordering = ["-date"] diff --git a/osmchadjango/supervise/serializers.py b/osmchadjango/supervise/serializers.py index 975259cc..944198a4 100644 --- a/osmchadjango/supervise/serializers.py +++ b/osmchadjango/supervise/serializers.py @@ -3,9 +3,12 @@ from rest_framework_gis.serializers import GeoFeatureModelSerializer from rest_framework_gis.fields import GeometryField from rest_framework.fields import ( - SerializerMethodField, HiddenField, CurrentUserDefault, DateTimeField, - ReadOnlyField - ) + SerializerMethodField, + HiddenField, + CurrentUserDefault, + DateTimeField, + ReadOnlyField, +) from rest_framework.validators import ValidationError, UniqueTogetherValidator from rest_framework.serializers import ModelSerializer @@ -19,46 +22,43 @@ class AreaOfInterestSerializer(GeoFeatureModelSerializer): class Meta: model = AreaOfInterest - geo_field = 'geometry' - fields = [ - 'id', 'name', 'filters', 'geometry', 'date', 'changesets_url', 'user' - ] + geo_field = "geometry" + fields = ["id", "name", "filters", "geometry", "date", "changesets_url", "user"] validators = [ UniqueTogetherValidator( - queryset=AreaOfInterest.objects.all(), - fields=('name', 'user') - ) - ] + queryset=AreaOfInterest.objects.all(), fields=("name", "user") + ) + ] def get_changesets_url(self, obj): - return reverse('supervise:aoi-list-changesets', args=[obj.id]) + return reverse("supervise:aoi-list-changesets", args=[obj.id]) def validate(self, data): - if data.get('filters') is None and data.get('geometry') is None: + if data.get("filters") is None and data.get("geometry") is None: raise ValidationError( - 'Set a value to the filters field or to the geometry to be able to save the AoI' - ) + "Set a value to the filters field or to the geometry to be able to save the AoI" + ) return data class AreaOfInterestAnonymousSerializer(AreaOfInterestSerializer): """Serializer to be used when an anonymous user access the AoI.""" + def to_representation(self, instance): - data = super( - AreaOfInterestAnonymousSerializer, - self - ).to_representation(instance) - for key in ['users', 'uids', 'checked_by']: - if key in data['properties']['filters'].keys(): - data['properties']['filters'].pop(key) + data = super(AreaOfInterestAnonymousSerializer, self).to_representation( + instance + ) + for key in ["users", "uids", "checked_by"]: + if key in data["properties"]["filters"].keys(): + data["properties"]["filters"].pop(key) return data class BlacklistSerializer(ModelSerializer): date = DateTimeField(read_only=True) - added_by = ReadOnlyField(source='added_by.username') + added_by = ReadOnlyField(source="added_by.username") class Meta: model = BlacklistedUser - fields = ('uid', 'username', 'date', 'added_by') + fields = ("uid", "username", "date", "added_by") diff --git a/osmchadjango/supervise/urls.py b/osmchadjango/supervise/urls.py index e161726d..cf1477f5 100644 --- a/osmchadjango/supervise/urls.py +++ b/osmchadjango/supervise/urls.py @@ -1,46 +1,42 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import, unicode_literals - -from django.urls import re_path +from django.urls import path from . import views - app_name = 'supervise' urlpatterns = [ - re_path( - r'^aoi/$', + path( + 'aoi/', view=views.AOIListCreateAPIView.as_view(), name='aoi-list-create' - ), - re_path( - r'^aoi/(?P[0-9a-f-]+)/$', + ), + path( + 'aoi//', view=views.AOIRetrieveUpdateDestroyAPIView.as_view(), name='aoi-detail' - ), - re_path( - r'^aoi/(?P[0-9a-f-]+)/changesets/$', + ), + path( + 'aoi//changesets/', view=views.AOIListChangesetsAPIView.as_view(), name='aoi-list-changesets' - ), - re_path( - r'^aoi/(?P[0-9a-f-]+)/changesets/feed/$', + ), + path( + 'aoi//changesets/feed/', view=views.AOIListChangesetsFeedView(), name='aoi-changesets-feed' - ), - re_path( - r'^aoi/(?P[0-9a-f-]+)/stats/$', + ), + path( + 'aoi//stats/', view=views.AOIStatsAPIView.as_view(), name='aoi-stats' - ), - re_path( - r'^blacklisted-users/$', + ), + path( + 'blacklisted-users/', view=views.BlacklistedUserListCreateAPIView.as_view(), name='blacklist-list-create' - ), - re_path( - r'^blacklisted-users/(?P[0-9a-f-]+)/$', + ), + path( + 'blacklisted-users//', view=views.BlacklistedUserDetailAPIView.as_view(), name='blacklist-detail' - ), - ] + ), +] diff --git a/osmchadjango/supervise/views.py b/osmchadjango/supervise/views.py index 8c8645ad..88b05e9e 100644 --- a/osmchadjango/supervise/views.py +++ b/osmchadjango/supervise/views.py @@ -1,37 +1,38 @@ -from __future__ import unicode_literals - from django.contrib.gis.geos import GEOSGeometry, Polygon from django.contrib.gis.feeds import Feed from django.urls import reverse from rest_framework.generics import ( - ListCreateAPIView, ListAPIView, RetrieveUpdateDestroyAPIView, - get_object_or_404 - ) + ListCreateAPIView, + ListAPIView, + RetrieveUpdateDestroyAPIView, + get_object_or_404, +) from rest_framework.response import Response from rest_framework.filters import OrderingFilter -from rest_framework.permissions import ( - IsAuthenticated, BasePermission, SAFE_METHODS - ) - -from ..changeset.serializers import ( - ChangesetSerializer, ChangesetSerializerToStaff, ChangesetStatsSerializer - ) -from ..changeset.views import StandardResultsSetPagination +from rest_framework.permissions import IsAuthenticated, BasePermission, SAFE_METHODS + +from osmchadjango.changeset.serializers import ( + ChangesetSerializer, + ChangesetSerializerToStaff, + ChangesetStatsSerializer, +) +from osmchadjango.changeset.views import StandardResultsSetPagination from .models import AreaOfInterest, BlacklistedUser from .serializers import ( - AreaOfInterestSerializer, BlacklistSerializer, - AreaOfInterestAnonymousSerializer - ) + AreaOfInterestSerializer, + BlacklistSerializer, + AreaOfInterestAnonymousSerializer, +) def get_geometry_from_filters(data): - if 'filters' in data.keys(): - if 'geometry' in data['filters'].keys(): - geometry = data['filters'].get('geometry') - return GEOSGeometry('{}'.format(geometry)) - elif 'in_bbox' in data['filters'].keys(): - geometry = data['filters'].get('in_bbox').split(',') + if "filters" in data.keys(): + if "geometry" in data["filters"].keys(): + geometry = data["filters"].get("geometry") + return GEOSGeometry(f"{geometry}") + elif "in_bbox" in data["filters"].keys(): + geometry = data["filters"].get("in_bbox").split(",") return Polygon.from_bbox(geometry) else: return None @@ -62,11 +63,12 @@ class AOIListCreateAPIView(ListCreateAPIView): It needs to receive the filter parameters and a name. The AoI name must be unique to a user. This endpoint requires authentication. """ + permission_classes = (IsAuthenticated,) serializer_class = AreaOfInterestSerializer filter_backends = (OrderingFilter,) - ordering_fields = ('date', 'name') - ordering = '-date' + ordering_fields = ("date", "name") + ordering = "-date" def get_queryset(self): if self.request: @@ -77,8 +79,8 @@ def get_queryset(self): def perform_create(self, serializer): serializer.save( user=self.request.user, - geometry=get_geometry_from_filters(self.request.data) - ) + geometry=get_geometry_from_filters(self.request.data), + ) class AOIRetrieveUpdateDestroyAPIView(RetrieveUpdateDestroyAPIView): @@ -98,13 +100,12 @@ class AOIRetrieveUpdateDestroyAPIView(RetrieveUpdateDestroyAPIView): Delete an Area of Interest. Only the user that created an Area of Interest has permissions to delete it. """ + queryset = AreaOfInterest.objects.all() permission_classes = (IsAuthenticated, IsOwnerOrReadOnly) def perform_update(self, serializer): - serializer.save( - geometry=get_geometry_from_filters(self.request.data) - ) + serializer.save(geometry=get_geometry_from_filters(self.request.data)) def get_serializer_class(self): if self.request and self.request.user.is_authenticated: @@ -114,35 +115,31 @@ def get_serializer_class(self): class AOIListChangesetsFeedView(Feed): - """Feed view with the last 50 changesets that matches an Area of Interest. - """ + """Feed view with the last 50 changesets that matches an Area of Interest.""" def get_object(self, request, pk): self.feed_id = pk return AreaOfInterest.objects.get(pk=pk) def title(self, obj): - return 'Changesets of Area of Interest {} by {}'.format( - obj.name if obj.name else 'Unnamed', obj.user - ) + return f"Changesets of Area of Interest {obj.name if obj.name else 'Unnamed'} by {obj.user}" def link(self, obj): - return reverse('supervise:aoi-detail', args=[obj.id]) + return reverse("supervise:aoi-detail", args=[obj.id]) def items(self, obj): return obj.changesets()[:50] def item_title(self, item): - return 'Changeset {} by {}'.format(item.id, item.user) + return f"Changeset {item.id} by {item.user}" def item_geometry(self, item): return item.bbox def item_link(self, item): - return '{}{}'.format(reverse( - 'frontend:changeset-detail', - args=[item.id] - ), '?aoi={}'.format(self.feed_id)) + return ( + f"{reverse('frontend:changeset-detail', args=[item.id])}?aoi={self.feed_id}" + ) def item_pubdate(self, item): return item.date @@ -151,24 +148,24 @@ def item_description(self, item): description_items = [] if item.comment: description_items.append(item.comment) - description_items.append('Create: {}, Modify: {}, Delete: {}'.format( - item.create, item.modify, item.delete - )) + description_items.append( + f"Create: {item.create}, Modify: {item.modify}, Delete: {item.delete}" + ) if item.is_suspect: - suspect = 'Changeset flagged for: ' - suspect += ', '.join([reason.name for reason in item.reasons.all()]) + suspect = "Changeset flagged for: " + suspect += ", ".join([reason.name for reason in item.reasons.all()]) description_items.append(suspect) if item.checked: if item.harmful: description_items.append( - 'Marked as harmful by {}'.format(item.check_user.username) - ) + f"Marked as harmful by {item.check_user.username}" + ) elif item.harmful is False: description_items.append( - 'Marked as good by {}'.format(item.check_user.username) - ) + f"Marked as good by {item.check_user.username}" + ) - return '
'.join(description_items) + return "
".join(description_items) class AOIListChangesetsAPIView(ListAPIView): @@ -176,6 +173,7 @@ class AOIListChangesetsAPIView(ListAPIView): geometry of an Area Of Interest. It supports pagination and return the data in the same way as the changeset list endpoint. """ + queryset = AreaOfInterest.objects.all() pagination_class = StandardResultsSetPagination permission_classes = (IsAuthenticated,) @@ -187,9 +185,12 @@ def get_serializer_class(self): return ChangesetSerializer def list(self, request, *args, **kwargs): - queryset = self.get_object().changesets().select_related( - 'check_user' - ).prefetch_related('tags', 'reasons') + queryset = ( + self.get_object() + .changesets() + .select_related("check_user") + .prefetch_related("tags", "reasons") + ) page = self.paginate_queryset(queryset) if page is not None: @@ -204,13 +205,17 @@ class AOIStatsAPIView(ListAPIView): """Return the statistics of the changesets that matches an Area of Interest. The data will be in the same format as the Changeset Stats view. """ + queryset = AreaOfInterest.objects.all() serializer_class = ChangesetStatsSerializer def list(self, request, *args, **kwargs): - queryset = self.get_object().changesets().select_related( - 'check_user' - ).prefetch_related('tags', 'reasons') + queryset = ( + self.get_object() + .changesets() + .select_related("check_user") + .prefetch_related("tags", "reasons") + ) serializer = self.get_serializer(queryset, many=True) return Response(serializer.data) @@ -225,6 +230,7 @@ class BlacklistedUserListCreateAPIView(ListCreateAPIView): Add a user to the Blacklist. Only staff users can add users to the blacklist. """ + queryset = BlacklistedUser.objects.all() serializer_class = BlacklistSerializer permission_classes = (IsAuthenticated,) @@ -253,6 +259,7 @@ class BlacklistedUserDetailAPIView(RetrieveUpdateDestroyAPIView): Update a BlacklistedUser. It's useful if you need to update the username of a User. """ + queryset = BlacklistedUser.objects.all() serializer_class = BlacklistSerializer permission_classes = (IsAuthenticated, IsOwnerOrReadOnly) @@ -263,7 +270,5 @@ def perform_update(self, serializer): def get_object(self): queryset = self.get_queryset() return get_object_or_404( - queryset, - added_by=self.request.user, - uid=self.kwargs['uid'] - ) + queryset, added_by=self.request.user, uid=self.kwargs["uid"] + ) diff --git a/osmchadjango/users/admin.py b/osmchadjango/users/admin.py index f038b2e2..c21c1e0c 100644 --- a/osmchadjango/users/admin.py +++ b/osmchadjango/users/admin.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import, unicode_literals - from django import forms from django.contrib import admin from django.contrib.auth.admin import UserAdmin as AuthUserAdmin @@ -24,7 +21,7 @@ def clean_username(self): User.objects.get(username=username) except User.DoesNotExist: return username - raise forms.ValidationError(self.error_messages['duplicate_username']) + raise forms.ValidationError(self.error_messages["duplicate_username"]) @admin.register(User) diff --git a/osmchadjango/users/management/commands/clear_tokens.py b/osmchadjango/users/management/commands/clear_tokens.py index 68d46728..d69e0763 100644 --- a/osmchadjango/users/management/commands/clear_tokens.py +++ b/osmchadjango/users/management/commands/clear_tokens.py @@ -8,4 +8,4 @@ class Command(BaseCommand): def handle(self, *args, **options): Token.objects.all().delete() - print('All user Tokens were deleted.') + print("All user Tokens were deleted.") diff --git a/osmchadjango/users/management/commands/update_user_names.py b/osmchadjango/users/management/commands/update_user_names.py index d5e4b9e7..c065f8de 100644 --- a/osmchadjango/users/management/commands/update_user_names.py +++ b/osmchadjango/users/management/commands/update_user_names.py @@ -1,13 +1,12 @@ from django.core.management.base import BaseCommand -from ...utils import update_user_name -from ...models import User +from osmchadjango.users.utils import update_user_name +from osmchadjango.users.models import User class Command(BaseCommand): - help = """Update the name field of all users with they current username in - OSM.""" + help = """Update the name field of all users with they current username in OSM.""" def handle(self, *args, **options): [update_user_name(user) for user in User.objects.all()] - print('Usernames updated.') + print("Usernames updated.") diff --git a/osmchadjango/users/models.py b/osmchadjango/users/models.py index 40b677c5..b2c0ebba 100644 --- a/osmchadjango/users/models.py +++ b/osmchadjango/users/models.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals, absolute_import - from django.contrib.auth.models import AbstractUser from django.db import models from django.contrib.postgres.fields import JSONField @@ -8,21 +5,12 @@ class User(AbstractUser): # First Name and Last Name do not cover name patterns around the globe. - name = models.CharField( - "Name of User", blank=True, max_length=255, db_index=True - ) - message_good = models.TextField( - "Default message to good changesets", - blank=True - ) - message_bad = models.TextField( - "Default message to bad changesets", - blank=True - ) + name = models.CharField("Name of User", blank=True, max_length=255, db_index=True) + message_good = models.TextField("Default message to good changesets", blank=True) + message_bad = models.TextField("Default message to bad changesets", blank=True) comment_feature = models.BooleanField( - "Enable suggestion to post comment", - default=False - ) + "Enable suggestion to post comment", default=False + ) def __str__(self): return self.username @@ -36,9 +24,9 @@ class MappingTeam(models.Model): created_by = models.ForeignKey(User, on_delete=models.CASCADE) def __str__(self): - return '{} by {}'.format(self.name, self.created_by.username) + return f"{self.name} by {self.created_by.username}" class Meta: - ordering = ['date'] - verbose_name = 'Mapping Team' - verbose_name_plural = 'Mapping Teams' + ordering = ["date"] + verbose_name = "Mapping Team" + verbose_name_plural = "Mapping Teams" diff --git a/osmchadjango/users/serializers.py b/osmchadjango/users/serializers.py index 1c19041b..356aaf0c 100644 --- a/osmchadjango/users/serializers.py +++ b/osmchadjango/users/serializers.py @@ -1,8 +1,11 @@ from django.contrib.auth import get_user_model from rest_framework.serializers import ( - ModelSerializer, Serializer, CharField, SlugRelatedField - ) + ModelSerializer, + Serializer, + CharField, + SlugRelatedField, +) from rest_framework.fields import ReadOnlyField from rest_framework.fields import SerializerMethodField @@ -14,22 +17,22 @@ class UserSerializer(ModelSerializer): avatar = SerializerMethodField() username = SerializerMethodField() whitelists = SlugRelatedField( - many=True, - read_only=True, - slug_field='whitelist_user' - ) + many=True, read_only=True, slug_field="whitelist_user" + ) def get_uid(self, obj): try: - return obj.social_auth.filter(provider='openstreetmap').last().uid + return obj.social_auth.filter(provider="openstreetmap").last().uid except AttributeError: return None def get_avatar(self, obj): try: - return obj.social_auth.filter( - provider='openstreetmap' - ).last().extra_data.get('avatar') + return ( + obj.social_auth.filter(provider="openstreetmap") + .last() + .extra_data.get("avatar") + ) except AttributeError: return None @@ -42,11 +45,19 @@ def get_username(self, obj): class Meta: model = get_user_model() fields = ( - 'id', 'uid', 'username', 'is_staff', 'is_active', 'email', - 'avatar', 'whitelists', 'message_good', 'message_bad', - 'comment_feature' - ) - read_only_fields = ('username', 'is_staff', 'is_active', 'id', 'uid') + "id", + "uid", + "username", + "is_staff", + "is_active", + "email", + "avatar", + "whitelists", + "message_good", + "message_bad", + "comment_feature", + ) + read_only_fields = ("username", "is_staff", "is_active", "id", "uid") class SocialSignUpSerializer(Serializer): @@ -56,9 +67,9 @@ class SocialSignUpSerializer(Serializer): class MappingTeamSerializer(ModelSerializer): - owner = ReadOnlyField(source='created_by.username', default=None) + owner = ReadOnlyField(source="created_by.username", default=None) class Meta: model = MappingTeam - fields = ('id', 'name', 'users', 'trusted', 'owner') - read_only_fields = ('trusted', 'owner') + fields = ("id", "name", "users", "trusted", "owner") + read_only_fields = ("trusted", "owner") diff --git a/osmchadjango/users/urls.py b/osmchadjango/users/urls.py index 280b92b1..0000e20d 100644 --- a/osmchadjango/users/urls.py +++ b/osmchadjango/users/urls.py @@ -1,46 +1,35 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import, unicode_literals - -from django.urls import re_path +from django.urls import path from . import views -app_name = 'users' +app_name = "users" urlpatterns = [ - re_path( - r'^users/$', - view=views.CurrentUserDetailAPIView.as_view(), - name='detail' - ), - re_path( - r'^social-auth/$', - view=views.SocialAuthAPIView.as_view(), - name="social-auth" - ), - re_path( - r'^update-deleted-users/$', + path("users/", view=views.CurrentUserDetailAPIView.as_view(), name="detail"), + path("social-auth/", view=views.SocialAuthAPIView.as_view(), name="social-auth"), + path( + "update-deleted-users/", view=views.update_deleted_users, - name="update-deleted-users" - ), - re_path( - r'^mapping-team/$', + name="update-deleted-users", + ), + path( + "mapping-team/", views.MappingTeamListCreateAPIView.as_view(), - name="mapping-team" - ), - re_path( - r'^mapping-team/(?P\d+)/$', + name="mapping-team", + ), + path( + "mapping-team//", views.MappingTeamDetailAPIView.as_view(), - name="mapping-team-detail" - ), - re_path( - r'^mapping-team/(?P\d+)/trust/$', - view=views.MappingTeamTrustingAPIView.as_view({'put': 'set_trusted'}), - name='trust-mapping-team' + name="mapping-team-detail", + ), + path( + "mapping-team//trust/", + view=views.MappingTeamTrustingAPIView.as_view({"put": "set_trusted"}), + name="trust-mapping-team", ), - re_path( - r'^mapping-team/(?P\d+)/untrust/$', - view=views.MappingTeamTrustingAPIView.as_view({'put': 'set_untrusted'}), - name='untrust-mapping-team' + path( + "mapping-team//untrust/", + view=views.MappingTeamTrustingAPIView.as_view({"put": "set_untrusted"}), + name="untrust-mapping-team", ), - ] +] diff --git a/osmchadjango/users/utils.py b/osmchadjango/users/utils.py index f84559a7..615f2b44 100644 --- a/osmchadjango/users/utils.py +++ b/osmchadjango/users/utils.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- import xml.etree.ElementTree as ET from xml.etree.ElementTree import ParseError @@ -12,9 +11,9 @@ def save_real_username(backend, user, response, *args, **kwargs): It records the real username of the OSM user in the name field of the User model. """ - if backend.name == 'openstreetmap' and user.name != response.get('username'): - user.name = response.get('username') - user.save(update_fields=['name']) + if backend.name == "openstreetmap" and user.name != response.get("username"): + user.name = response.get("username") + user.save(update_fields=["name"]) def update_user_name(user): @@ -22,17 +21,15 @@ def update_user_name(user): user in our local database. """ try: - uid = user.social_auth.get(provider='openstreetmap').uid - url = 'https://www.openstreetmap.org/api/0.6/user/{}/'.format(uid) + uid = user.social_auth.get(provider="openstreetmap").uid + url = f"https://www.openstreetmap.org/api/0.6/user/{uid}/" data = ET.fromstring(requests.get(url).content) - display_name = data.find('user').get('display_name') + display_name = data.find("user").get("display_name") if user.name != display_name: user.name = display_name - user.save(update_fields=['name']) - print('User with uid {} updated successfully.'.format(uid)) + user.save(update_fields=["name"]) + print(f"User with uid {uid} updated successfully.") except UserSocialAuth.DoesNotExist: - print( - 'User {} does not have a social_auth instance.'.format(user.username) - ) + print(f"User {user.username} does not have a social_auth instance.") except ParseError: - print('It was not possible to update user with uid {}.'.format(uid)) + print(f"It was not possible to update user with uid {uid}.") diff --git a/osmchadjango/users/views.py b/osmchadjango/users/views.py index 341f0d5e..63c22c92 100644 --- a/osmchadjango/users/views.py +++ b/osmchadjango/users/views.py @@ -1,22 +1,27 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import, unicode_literals - from django.contrib.auth import get_user_model from django.conf import settings from rest_framework.authtoken.models import Token from rest_framework.generics import ( - ListCreateAPIView, RetrieveUpdateAPIView, RetrieveUpdateDestroyAPIView, - GenericAPIView - ) + ListCreateAPIView, + RetrieveUpdateAPIView, + RetrieveUpdateDestroyAPIView, + GenericAPIView, +) from rest_framework.viewsets import ModelViewSet from rest_framework.permissions import ( - IsAuthenticated, IsAdminUser, BasePermission, SAFE_METHODS - ) + IsAuthenticated, + IsAdminUser, + BasePermission, + SAFE_METHODS, +) from rest_framework import status from rest_framework.decorators import ( - api_view, parser_classes, permission_classes, action - ) + api_view, + parser_classes, + permission_classes, + action, +) from rest_framework.parsers import JSONParser, MultiPartParser, FormParser from rest_framework.response import Response from social_django.utils import load_strategy, load_backend @@ -24,11 +29,9 @@ from django_filters import rest_framework as filters from django_filters.widgets import BooleanWidget -from .serializers import ( - UserSerializer, SocialSignUpSerializer, MappingTeamSerializer - ) +from .serializers import UserSerializer, SocialSignUpSerializer, MappingTeamSerializer from .models import MappingTeam -from ..changeset.models import Changeset +from osmchadjango.changeset.models import Changeset User = get_user_model() @@ -54,6 +57,7 @@ class CurrentUserDetailAPIView(RetrieveUpdateAPIView): put: Update details of the current logged user. """ + permission_classes = (IsAuthenticated,) serializer_class = UserSerializer model = get_user_model() @@ -71,15 +75,15 @@ class SocialAuthAPIView(GenericAPIView): `oauth_verifier` to receive the `token` that you need to make authenticated requests in all OSMCHA endpoints. """ + queryset = User.objects.all() serializer_class = SocialSignUpSerializer - base_url = 'https://www.openstreetmap.org/oauth' - request_token_url = '{}/request_token?oauth_callback={}'.format( - base_url, - settings.OAUTH_REDIRECT_URI - ) - access_token_url = '{}/access_token'.format(base_url) + base_url = "https://www.openstreetmap.org/oauth" + request_token_url = ( + f"{base_url}/request_token?oauth_callback={settings.OAUTH_REDIRECT_URI}" + ) + access_token_url = f"{base_url}/access_token" def get_access_token(self, oauth_token, oauth_token_secret, oauth_verifier): oauth = OAuth1Session( @@ -87,63 +91,61 @@ def get_access_token(self, oauth_token, oauth_token_secret, oauth_verifier): client_secret=settings.SOCIAL_AUTH_OPENSTREETMAP_SECRET, resource_owner_key=oauth_token, resource_owner_secret=oauth_token_secret, - verifier=oauth_verifier - ) + verifier=oauth_verifier, + ) return oauth.fetch_access_token(self.access_token_url) def get_user_token(self, request, access_token): backend = load_backend( - strategy=load_strategy(request), - name='openstreetmap', - redirect_uri=None - ) + strategy=load_strategy(request), name="openstreetmap", redirect_uri=None + ) authed_user = request.user if not request.user.is_anonymous else None user = backend.do_auth(access_token, user=authed_user) token, created = Token.objects.get_or_create(user=user) - return {'token': token.key} + return {"token": token.key} def post(self, request, *args, **kwargs): - if 'oauth_token' not in request.data.keys() or not request.data['oauth_token']: + if "oauth_token" not in request.data.keys() or not request.data["oauth_token"]: consumer = OAuth1Session( settings.SOCIAL_AUTH_OPENSTREETMAP_KEY, - client_secret=settings.SOCIAL_AUTH_OPENSTREETMAP_SECRET - ) - request_token = consumer.fetch_request_token( - self.request_token_url - ) - return Response({ - 'oauth_token': request_token['oauth_token'], - 'oauth_token_secret': request_token['oauth_token_secret'] - }) + client_secret=settings.SOCIAL_AUTH_OPENSTREETMAP_SECRET, + ) + request_token = consumer.fetch_request_token(self.request_token_url) + return Response( + { + "oauth_token": request_token["oauth_token"], + "oauth_token_secret": request_token["oauth_token_secret"], + } + ) else: serializer = self.get_serializer(data=request.data) serializer.is_valid(raise_exception=True) access_token = self.get_access_token( - request.data['oauth_token'], - request.data['oauth_token_secret'], - request.data['oauth_verifier'] - ) + request.data["oauth_token"], + request.data["oauth_token_secret"], + request.data["oauth_verifier"], + ) return Response(self.get_user_token(request, access_token)) class MappingTeamFilter(filters.FilterSet): trusted = filters.BooleanFilter( - field_name='trusted', + field_name="trusted", widget=BooleanWidget(), - help_text="""Filter Mapping Teams that were trusted by a staff user.""" - ) + help_text="""Filter Mapping Teams that were trusted by a staff user.""", + ) name = filters.CharFilter( - field_name='name', - lookup_expr='icontains', + field_name="name", + lookup_expr="icontains", help_text="""Filter Mapping Teams by its name field using the icontains - lookup expression.""" - ) + lookup expression.""", + ) owner = filters.CharFilter( - field_name='created_by__username', - lookup_expr='exact', + field_name="created_by__username", + lookup_expr="exact", help_text="""Filter Mapping Teams by the username of the user that - created it. This field uses the exact lookup expression.""" - ) + created it. This field uses the exact lookup expression.""", + ) class Meta: model = MappingTeam @@ -152,6 +154,7 @@ class Meta: class MappingTeamListCreateAPIView(ListCreateAPIView): """List and create Mapping teams.""" + queryset = MappingTeam.objects.all() serializer_class = MappingTeamSerializer permission_classes = (IsAuthenticated,) @@ -161,14 +164,18 @@ class MappingTeamListCreateAPIView(ListCreateAPIView): def perform_create(self, serializer): serializer.save( created_by=self.request.user, - ) + ) class MappingTeamDetailAPIView(RetrieveUpdateDestroyAPIView): """List and create Mapping teams.""" + queryset = MappingTeam.objects.all() serializer_class = MappingTeamSerializer - permission_classes = (IsAuthenticated, IsOwnerAdminOrReadOnly,) + permission_classes = ( + IsAuthenticated, + IsOwnerAdminOrReadOnly, + ) class MappingTeamTrustingAPIView(ModelViewSet): @@ -180,19 +187,13 @@ def update_team(self, team, request, trusted): """Update 'checked', 'harmful', 'check_user', 'check_date' fields of the changeset and return a 200 response""" team.trusted = trusted - team.save( - update_fields=['trusted'] - ) + team.save(update_fields=["trusted"]) return Response( - { - 'detail': 'Mapping Team set as {}.'.format( - 'trusted' if trusted else 'untrusted' - ) - }, - status=status.HTTP_200_OK - ) + {"detail": f"Mapping Team set as {'trusted' if trusted else 'untrusted'}."}, + status=status.HTTP_200_OK, + ) - @action(detail=True, methods=['put']) + @action(detail=True, methods=["put"]) def set_trusted(self, request, pk): """Set a Mapping Team as trusted. You don't need to send data, just make an empty PUT request. @@ -200,12 +201,12 @@ def set_trusted(self, request, pk): team = self.get_object() if team.trusted: return Response( - {'detail': 'Mapping team is already trusted.'}, - status=status.HTTP_403_FORBIDDEN - ) + {"detail": "Mapping team is already trusted."}, + status=status.HTTP_403_FORBIDDEN, + ) return self.update_team(team, request, trusted=True) - @action(detail=True, methods=['put']) + @action(detail=True, methods=["put"]) def set_untrusted(self, request, pk): """Set a Mapping Team as untrusted. You don't need to send data, just make an empty PUT request. @@ -213,13 +214,13 @@ def set_untrusted(self, request, pk): team = self.get_object() if team.trusted is False: return Response( - {'detail': 'Mapping team is already untrusted.'}, - status=status.HTTP_403_FORBIDDEN - ) + {"detail": "Mapping team is already untrusted."}, + status=status.HTTP_403_FORBIDDEN, + ) return self.update_team(team, request, trusted=False) -@api_view(['POST']) +@api_view(["POST"]) @parser_classes((JSONParser, MultiPartParser, FormParser)) @permission_classes((IsAuthenticated, IsAdminUser)) def update_deleted_users(request): @@ -230,24 +231,24 @@ def update_deleted_users(request): users have permissions to use this endpoint. """ - if request.data and request.data.get('uids'): - uids = [str(uid) for uid in request.data.get('uids')] + if request.data and request.data.get("uids"): + uids = [str(uid) for uid in request.data.get("uids")] for uid in uids: - Changeset.objects.filter(uid=uid).update( - user='user_{}'.format(uid) - ) + Changeset.objects.filter(uid=uid).update(user=f"user_{uid}") try: user = User.objects.get(social_auth__uid=uid) - user.username = 'user_{}'.format(uid) + user.username = f"user_{uid}" user.save() except User.DoesNotExist: pass return Response( - {'detail': 'Changesets updated and user renamed.'}, - status=status.HTTP_200_OK - ) + {"detail": "Changesets updated and user renamed."}, + status=status.HTTP_200_OK, + ) else: return Response( - {'detail': 'Payload is missing the `uids` field or it has an incorrect value.'}, - status=status.HTTP_400_BAD_REQUEST - ) + { + "detail": "Payload is missing the `uids` field or it has an incorrect value." + }, + status=status.HTTP_400_BAD_REQUEST, + )