├── .editorconfig ├── .gitattributes ├── .github ├── dependabot.yml └── workflows │ ├── django.yml │ └── docker-publish.yml ├── .gitignore ├── CHANGELOG.rst ├── CODE_OF_CONDUCT.md ├── DEPLOY.rst ├── Dockerfile ├── LICENSE ├── README.rst ├── compose ├── django │ ├── entrypoint.sh │ └── start-dev.sh └── postgres │ ├── Dockerfile │ ├── backup.sh │ ├── list-backups.sh │ └── restore.sh ├── config ├── __init__.py ├── settings │ ├── __init__.py │ ├── aws_production.py │ ├── common.py │ ├── local.py │ ├── production.py │ └── tests.py ├── urls.py └── wsgi.py ├── docker-compose.yml ├── docs ├── Makefile ├── __init__.py ├── conf.py ├── deploy.rst ├── docker_ec2.rst ├── index.rst ├── install.rst └── make.bat ├── env.example ├── install_os_dependencies.sh ├── install_python_dependencies.sh ├── manage.py ├── osmchadjango ├── __init__.py ├── changeset │ ├── __init__.py │ ├── admin.py │ ├── filters.py │ ├── fixtures │ │ └── brazil.geojson │ ├── management │ │ ├── __init__.py │ │ └── commands │ │ │ ├── __init__.py │ │ │ ├── backfill_changesets.py │ │ │ ├── delete_old_data.py │ │ │ ├── fetchchangesets.py │ │ │ ├── import_file.py │ │ │ ├── mark_harmful_changeset.py │ │ │ └── merge_reasons.py │ ├── migrations │ │ ├── 0001_initial.py │ │ ├── 0002_auto_20150804_0119.py │ │ ├── 0003_auto_20150804_0126.py │ │ ├── 0004_auto_20150828_0128.py │ │ ├── 0005_auto_20150828_1942.py │ │ ├── 0006_changeset_imagery_used.py │ │ ├── 0007_auto_20150828_1947.py │ │ ├── 0008_changeset_uid.py │ │ ├── 0009_auto_20151226_0012.py │ │ ├── 0010_auto_20151227_1306.py │ │ ├── 0011_import.py │ │ ├── 0012_auto_20160125_1019.py │ │ ├── 0013_auto_20160125_1021.py │ │ ├── 0014_auto_20160216_1029.py │ │ ├── 0015_auto_20160217_0511.py │ │ ├── 0016_auto_20160217_1004.py │ │ ├── 0017_auto_20160217_1102.py │ │ ├── 0018_auto_20160218_0218.py │ │ ├── 0019_auto_20160222_0726.py │ │ ├── 0020_auto_20160222_2342.py │ │ ├── 0021_auto_20160222_2345.py │ │ ├── 0022_auto_20160222_2358.py │ │ ├── 0023_userdetail_contributor_uid.py │ │ ├── 0024_auto_20160226_0738.py │ │ ├── 0025_suspiciousfeature.py │ │ ├── 0026_auto_20160314_1227.py │ │ ├── 0027_auto_20160321_0605.py │ │ ├── 0028_auto_20160321_1035.py │ │ ├── 0029_suspiciousfeature_timestamp.py │ │ ├── 0030_auto_20170201_1617.py │ │ ├── 0031_remove_changeset_score.py │ │ ├── 0032_auto_20170210_0013.py │ │ ├── 0033_suspicionreasons_description.py │ │ ├── 0034_suspicionreasons_is_visible.py │ │ ├── 0035_auto_20170224_0116.py │ │ ├── 0036_harmfulreason.py │ │ ├── 0037_auto_20170224_1845.py │ │ ├── 0038_changeset_harmful_reasons.py │ │ ├── 0039_auto_20170301_0236.py │ │ ├── 0040_auto_20170311_0228.py │ │ ├── 0040_auto_20170328_1350.py │ │ ├── 0041_auto_20170317_0238.py │ │ ├── 0041_auto_20170330_1401.py │ │ ├── 0042_auto_20170330_1402.py │ │ ├── 0043_auto_20170330_1402.py │ │ ├── 0044_merge_20170330_1409.py │ │ ├── 0045_auto_20170410_1218.py │ │ ├── 0046_auto_20170414_2043.py │ │ ├── 0047_auto_20170417_1718.py │ │ ├── 0048_changeset_area.py │ │ ├── 0049_auto_20180307_1417.py │ │ ├── 0050_auto_20181008_1001.py │ │ ├── 0051_changeset_metadata.py │ │ ├── 0052_changeset_comments_count.py │ │ ├── 0053_auto_20201105_1229.py │ │ ├── 0054_changeset_reviewed_features.py │ │ ├── 0055_changeset_tag_changes.py │ │ ├── 0056_auto_20220429_1305.py │ │ ├── 0057_auto_20220920_1423.py │ │ └── __init__.py │ ├── models.py │ ├── serializers.py │ ├── tasks.py │ ├── templates │ │ └── changeset │ │ │ ├── bbox_map.html │ │ │ └── not_allowed.html │ ├── tests │ │ ├── __init__.py │ │ ├── modelfactories.py │ │ ├── test_add_feature_views.py │ │ ├── test_changeset_views.py │ │ ├── test_comment_views.py │ │ ├── test_filters.py │ │ ├── test_fixtures │ │ │ ├── 011.osm.gz │ │ │ ├── way-23-with-unvisible-reason.json │ │ │ ├── way-23.json │ │ │ └── way-24.json │ │ ├── test_management_commands.py │ │ ├── test_models.py │ │ ├── test_reasons_tags_views.py │ │ ├── test_review_features_views.py │ │ ├── test_stats_views.py │ │ ├── test_tag_changes_view.py │ │ ├── test_tasks.py │ │ └── test_whitelist_views.py │ ├── throttling.py │ ├── urls.py │ └── views.py ├── contrib │ ├── __init__.py │ └── sites │ │ ├── __init__.py │ │ └── migrations │ │ ├── 0001_initial.py │ │ ├── 0002_set_site_domain_and_name.py │ │ ├── 0003_auto_20151124_0124.py │ │ ├── 0003_auto_20161005_1234.py │ │ ├── 0004_merge_20170130_1414.py │ │ └── __init__.py ├── roulette_integration │ ├── __init__.py │ ├── admin.py │ ├── apps.py │ ├── migrations │ │ ├── 0001_initial.py │ │ └── __init__.py │ ├── models.py │ ├── serializers.py │ ├── tests │ │ ├── __init__.py │ │ ├── test_models.py │ │ ├── test_utils.py │ │ └── test_views.py │ ├── urls.py │ ├── utils.py │ └── views.py ├── supervise │ ├── __init__.py │ ├── admin.py │ ├── apps.py │ ├── migrations │ │ ├── 0001_initial.py │ │ ├── 0002_auto_20170227_1257.py │ │ ├── 0003_auto_20170227_1320.py │ │ ├── 0004_auto_20170405_2012.py │ │ ├── 0005_auto_20170405_2327.py │ │ ├── 0006_auto_20170405_2328.py │ │ ├── 0007_auto_20170504_2109.py │ │ ├── 0008_auto_20170504_2119.py │ │ ├── 0009_blacklisteduser.py │ │ ├── 0010_areaofinterest_date.py │ │ ├── 0011_auto_20170720_1914.py │ │ ├── 0012_populate_uid_field.py │ │ ├── 0013_auto_20170720_1954.py │ │ ├── 0014_auto_20180307_1417.py │ │ ├── 0015_auto_20181129_1135.py │ │ └── __init__.py │ ├── models.py │ ├── serializers.py │ ├── tests │ │ ├── __init__.py │ │ ├── test_models.py │ │ └── test_views.py │ ├── urls.py │ └── views.py ├── templates │ ├── account │ │ ├── base.html │ │ ├── email.html │ │ ├── email_confirm.html │ │ ├── email_confirmed.html │ │ ├── login.html │ │ ├── logout.html │ │ ├── password_change.html │ │ ├── password_reset.html │ │ ├── password_reset_done.html │ │ ├── password_reset_from_key.html │ │ ├── password_reset_from_key_done.html │ │ ├── password_set.html │ │ ├── signup.html │ │ ├── signup_closed.html │ │ ├── verification_sent.html │ │ └── verified_email_required.html │ └── pages │ │ ├── about.html │ │ └── home.html └── users │ ├── __init__.py │ ├── admin.py │ ├── management │ ├── __init__.py │ └── commands │ │ ├── __init__.py │ │ ├── clear_tokens.py │ │ └── update_user_names.py │ ├── migrations │ ├── 0001_initial.py │ ├── 0002_auto_20151124_0124.py │ ├── 0002_auto_20161005_1234.py │ ├── 0003_merge_20170130_1414.py │ ├── 0004_auto_20170201_1617.py │ ├── 0005_auto_20170328_1352.py │ ├── 0006_auto_20170330_1401.py │ ├── 0007_auto_20170421_1939.py │ ├── 0008_auto_20180118_1229.py │ ├── 0009_auto_20180307_1417.py │ ├── 0010_auto_20190123_1129.py │ ├── 0011_auto_20190206_1257.py │ ├── 0012_auto_20240523_0227.py │ └── __init__.py │ ├── models.py │ ├── serializers.py │ ├── tests │ ├── __init__.py │ ├── test_management.py │ ├── test_models.py │ ├── test_utils.py │ └── test_views.py │ ├── urls.py │ ├── utils.py │ └── views.py ├── requirements.apt ├── requirements.txt ├── requirements ├── base.txt ├── local.txt ├── production.txt └── test.txt └── setup.cfg /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | 3 | root = true 4 | 5 | [*] 6 | charset = utf-8 7 | end_of_line = lf 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [*.{py,rst,ini}] 12 | indent_style = space 13 | indent_size = 4 14 | 15 | [*.py] 16 | # https://github.com/timothycrosley/isort/wiki/isort-Settings 17 | line_length=120 18 | known_first_party=osmcha-django 19 | multi_line_output=3 20 | default_section=THIRDPARTY 21 | 22 | [*.yml] 23 | indent_style = space 24 | indent_size = 2 25 | 26 | [*.md] 27 | trim_trailing_whitespace = false 28 | 29 | [Makefile] 30 | indent_style = tab 31 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto 2 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: pip 4 | directory: "/" 5 | schedule: 6 | interval: daily 7 | time: "08:00" 8 | open-pull-requests-limit: 10 9 | -------------------------------------------------------------------------------- /.github/workflows/django.yml: -------------------------------------------------------------------------------- 1 | name: Django CI 2 | 3 | on: pull_request 4 | 5 | jobs: 6 | build: 7 | runs-on: ubuntu-latest 8 | strategy: 9 | max-parallel: 4 10 | matrix: 11 | python-version: ['3.9', '3.10'] 12 | 13 | steps: 14 | - uses: actions/checkout@v2 15 | - name: Setup PostgreSQL with Postgis 16 | uses: huaxk/postgis-action@v1.0.0 17 | with: 18 | postgresql version: latest 19 | postgresql db: osmcha 20 | postgresql user: ${{ secrets.POSTGRES_USER }} 21 | postgresql password: ${{ secrets.POSTGRES_PASSWORD }} 22 | - name: Set up Python ${{ matrix.python-version }} 23 | uses: actions/setup-python@v2 24 | with: 25 | python-version: ${{ matrix.python-version }} 26 | - name: Install Dependencies 27 | run: | 28 | sudo apt-get update -qq -y 29 | sudo apt-get install -y binutils libproj-dev python3-gdal libgeos-dev libyaml-dev 30 | python -m pip install --upgrade pip 31 | pip install -r requirements/test.txt 32 | - name: Run Tests 33 | run: | 34 | coverage run manage.py test --settings=config.settings.tests 35 | env: 36 | OAUTH2_OSM_KEY: ${{ secrets.OAUTH_OSM_KEY }} 37 | OAUTH2_OSM_SECRET: ${{ secrets.OAUTH_OSM_SECRET }} 38 | PGUSER: ${{ secrets.POSTGRES_USER }} 39 | PGPASSWORD: ${{ secrets.POSTGRES_PASSWORD }} 40 | -------------------------------------------------------------------------------- /.github/workflows/docker-publish.yml: -------------------------------------------------------------------------------- 1 | name: Build Docker Image, Push to GHCR 2 | 3 | on: 4 | push: 5 | branches: [ "main" ] 6 | tags: [ 'v*.*.*' ] 7 | 8 | env: 9 | REGISTRY: ghcr.io 10 | IMAGE_NAME: osmcha/osmcha-django 11 | 12 | jobs: 13 | build: 14 | runs-on: ubuntu-latest 15 | 16 | steps: 17 | - name: Checkout code 18 | uses: actions/checkout@v4 19 | 20 | # Enable BuildKit for docker builds. This enables building 21 | # multi-platform images and exporting the layer cache 22 | # https://github.com/docker/setup-buildx-action 23 | - name: Set up Docker Buildx 24 | uses: docker/setup-buildx-action@v3 25 | 26 | # https://github.com/docker/login-action 27 | - name: Login to Registry ${{ env.REGISTRY }} 28 | uses: docker/login-action@v3 29 | with: 30 | registry: ${{ env.REGISTRY }} 31 | username: ${{ github.actor }} 32 | password: ${{ secrets.GITHUB_TOKEN }} 33 | 34 | # Extract metadata (tags, labels) for Docker 35 | # https://github.com/docker/metadata-action 36 | - name: Extract Docker metadata 37 | id: meta 38 | uses: docker/metadata-action@v5 39 | with: 40 | images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} 41 | 42 | # https://github.com/docker/build-push-action 43 | - name: Build and push Docker image 44 | uses: docker/build-push-action@v5 45 | with: 46 | context: . 47 | push: true 48 | tags: ${{ steps.meta.outputs.tags }} 49 | labels: ${{ steps.meta.outputs.labels }} 50 | cache-from: type=gha 51 | cache-to: type=gha,mode=max 52 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Python bytecode: 2 | *.py[co] 3 | 4 | # Packaging files: 5 | *.egg* 6 | 7 | # Sphinx docs: 8 | build 9 | 10 | # SQLite3 database files: 11 | *.db 12 | 13 | # Logs: 14 | *.log 15 | 16 | # Eclipse 17 | .project 18 | .pydevproject 19 | .settings 20 | 21 | # Linux Editors 22 | *~ 23 | \#*\# 24 | /.emacs.desktop 25 | /.emacs.desktop.lock 26 | .elc 27 | auto-save-list 28 | tramp 29 | .\#* 30 | *.swp 31 | *.swo 32 | *.nja 33 | 34 | # Mac 35 | .DS_Store 36 | ._* 37 | 38 | # Windows 39 | Thumbs.db 40 | Desktop.ini 41 | 42 | #tests 43 | .coverage 44 | 45 | #js 46 | node_modules/ 47 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | 2 | # Code of Conduct 3 | 4 | This project follows the [OpenStreetMap US Code of Conduct](https://wiki.openstreetmap.org/wiki/Foundation/Local_Chapters/United_States/Code_of_Conduct_Committee/OSM_US_Code_of_Conduct) 5 | 6 | -------------------------------------------------------------------------------- /DEPLOY.rst: -------------------------------------------------------------------------------- 1 | Deployment 2 | ------------ 3 | 4 | The Docker configuration settings allows you to deploy it fast. With ``docker`` running 5 | and ``docker-compose`` installed, copy the ``env.example`` to ``.env``, configure it with 6 | your settings and execute the following commands: 7 | 8 | .. code-block:: bash 9 | docker-compose build 10 | docker-compose up 11 | 12 | You can access your ``osmcha-django`` instance in http://localhost/ at your browser. 13 | 14 | It is also possible to deploy to Heroku or to your own server by using Dokku, an open 15 | source Heroku clone. 16 | 17 | If you find any issue, please report. We didn't test it in Heroku and Dokku 18 | 19 | Heroku 20 | ^^^^^^ 21 | 22 | Run these commands to deploy the project to Heroku: 23 | 24 | .. code-block:: bash 25 | 26 | heroku create --buildpack https://github.com/heroku/heroku-buildpack-python 27 | 28 | heroku addons:create heroku-postgresql:hobby-dev 29 | heroku pg:backups schedule --at '02:00 America/Los_Angeles' DATABASE_URL 30 | heroku pg:promote DATABASE_URL 31 | 32 | heroku addons:create heroku-redis:hobby-dev 33 | 34 | heroku config:set DJANGO_SECRET_KEY=`openssl rand -base64 32` 35 | heroku config:set DJANGO_SETTINGS_MODULE='config.settings.production' 36 | heroku config:set POSTGRES_USER='postgresuser' 37 | heroku config:set POSTGRES_PASSWORD='mysecretpass' 38 | heroku config:set OAUTH_OSM_KEY='your_osm_oauth_key' 39 | heroku config:set OAUTH_OSM_SECRET='your_osm_oauth_secret' 40 | 41 | heroku config:set PYTHONHASHSEED=random 42 | 43 | git push heroku master 44 | heroku run python manage.py migrate 45 | heroku run python manage.py check --deploy 46 | heroku run python manage.py createsuperuser 47 | heroku open 48 | 49 | Dokku 50 | ^^^^^ 51 | 52 | You need to make sure you have a server running Dokku with at least 1GB of RAM. Backing services are 53 | added just like in Heroku however you must ensure you have the relevant Dokku plugins installed. 54 | 55 | .. code-block:: bash 56 | 57 | cd /var/lib/dokku/plugins 58 | git clone https://github.com/rlaneve/dokku-link.git link 59 | git clone https://github.com/dokku/dokku-rabbitmq redis 60 | git clone https://github.com/jezdez/dokku-postgres-plugin postgres 61 | dokku plugins-install 62 | 63 | You can specify the buildpack you wish to use by creating a file name .env containing the following. 64 | 65 | .. code-block:: bash 66 | 67 | export BUILDPACK_URL= 68 | 69 | You can then deploy by running the following commands. 70 | 71 | .. code-block:: bash 72 | 73 | git remote add dokku dokku@yourservername.com:osmcha-django 74 | git push dokku master 75 | ssh -t dokku@yourservername.com dokku redis:create osmcha-django-redis 76 | ssh -t dokku@yourservername.com dokku redis:link osmcha-django-redis osmcha-django 77 | ssh -t dokku@yourservername.com dokku postgres:create osmcha-django-postgres 78 | ssh -t dokku@yourservername.com dokku postgres:link osmcha-django-postgres osmcha-django 79 | ssh -t dokku@yourservername.com dokku config:set osmcha-django DJANGO_SECRET_KEY=RANDOM_SECRET_KEY_HERE 80 | ssh -t dokku@yourservername.com dokku config:set osmcha-django DJANGO_SETTINGS_MODULE='config.settings.production' 81 | ssh -t dokku@yourservername.com dokku config:set osmcha-django POSTGRES_USER='postgresuser' 82 | ssh -t dokku@yourservername.com dokku config:set osmcha-django POSTGRES_PASSWORD='mysecretpass' 83 | ssh -t dokku@yourservername.com dokku config:set osmcha-django OAUTH_OSM_KEY='your_osm_oauth_key' 84 | ssh -t dokku@yourservername.com dokku config:set osmcha-django OAUTH_OSM_SECRET='your_osm_oauth_secret' 85 | ssh -t dokku@yourservername.com dokku run osmcha-django python manage.py migrate 86 | ssh -t dokku@yourservername.com dokku run osmcha-django python manage.py createsuperuser 87 | 88 | When deploying via Dokku make sure you backup your database in some fashion as it is NOT done automatically. 89 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.10-slim-bookworm 2 | ARG DEBIAN_FRONTEND=noninteractive 3 | ARG REQUIREMENTS_FILE=production.txt 4 | ARG DJANGO_SETTINGS_MODULE=config.settings.production 5 | 6 | RUN apt-get update -qq -y \ 7 | && apt-get install -y curl wget python3 python3-dev python3-pip git \ 8 | libgeos-dev libcurl4-gnutls-dev librtmp-dev python3-gdal libyaml-dev \ 9 | locales nginx supervisor postgresql-client libpq-dev \ 10 | && apt-get clean \ 11 | && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* 12 | 13 | # Requirements have to be pulled and installed here, otherwise caching won't work 14 | COPY ./requirements /requirements 15 | RUN pip install -r /requirements/$REQUIREMENTS_FILE 16 | 17 | COPY . /app 18 | RUN useradd django 19 | RUN chown -R django:django /app 20 | WORKDIR /app 21 | 22 | RUN python manage.py collectstatic --noinput 23 | 24 | COPY ./compose/django/entrypoint.sh /entrypoint.sh 25 | RUN chmod +x /entrypoint.sh 26 | 27 | USER django 28 | VOLUME /app/staticfiles 29 | 30 | # Default number of gunicorn worker processes. Using an env var instead of --workers 31 | # in the command below lets users of this docker image override the default easily. 32 | ENV WEB_CONCURRENCY 4 33 | 34 | ENTRYPOINT ["/entrypoint.sh"] 35 | CMD ["gunicorn", "config.wsgi", "-t", "120", "-b", "0.0.0.0:5000", "--access-logfile", "-"] 36 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright © 2015-2017 Wille Marcel and Mapbox, Inc. 2 | This code available under the terms of the BSD 2-Clause license. 3 | 4 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 5 | 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 6 | 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 7 | 8 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 9 | -------------------------------------------------------------------------------- /compose/django/entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | 4 | postgres_ready() { 5 | python << END 6 | import sys 7 | import psycopg2 8 | try: 9 | conn = psycopg2.connect("") # use environment variables for connection 10 | except psycopg2.OperationalError: 11 | sys.exit(-1) 12 | sys.exit(0) 13 | END 14 | } 15 | 16 | until postgres_ready; do 17 | >&2 echo "Postgres is unavailable - sleeping" 18 | sleep 1 19 | done 20 | 21 | >&2 echo "Postgres is up - continuing..." 22 | exec $@ 23 | -------------------------------------------------------------------------------- /compose/django/start-dev.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | python manage.py migrate 3 | python manage.py runserver_plus 0.0.0.0:5000 4 | -------------------------------------------------------------------------------- /compose/postgres/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM mdillon/postgis:9.4 2 | 3 | # add backup scripts 4 | ADD backup.sh /usr/local/bin/backup 5 | ADD restore.sh /usr/local/bin/restore 6 | ADD list-backups.sh /usr/local/bin/list-backups 7 | 8 | # make them executable 9 | RUN chmod +x /usr/local/bin/restore 10 | RUN chmod +x /usr/local/bin/list-backups 11 | RUN chmod +x /usr/local/bin/backup 12 | -------------------------------------------------------------------------------- /compose/postgres/backup.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # stop on errors 3 | set -e 4 | 5 | # we might run into trouble when using the default `postgres` user, e.g. when dropping the postgres 6 | # database in restore.sh. Check that something else is used here 7 | if [ "$POSTGRES_USER" == "postgres" ] 8 | then 9 | echo "creating a backup as the postgres user is not supported, make sure to set the POSTGRES_USER environment variable" 10 | exit 1 11 | fi 12 | 13 | # export the postgres password so that subsequent commands don't ask for it 14 | export PGPASSWORD=$POSTGRES_PASSWORD 15 | 16 | echo "creating backup" 17 | echo "---------------" 18 | 19 | FILENAME=backup_$(date +'%Y_%m_%dT%H_%M_%S').sql.gz 20 | pg_dump -h postgres -U $POSTGRES_USER | gzip > /backups/$FILENAME 21 | 22 | echo "successfully created backup $FILENAME" 23 | -------------------------------------------------------------------------------- /compose/postgres/list-backups.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | echo "listing available backups" 3 | echo "-------------------------" 4 | ls /backups/ 5 | -------------------------------------------------------------------------------- /compose/postgres/restore.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # stop on errors 4 | set -e 5 | 6 | # we might run into trouble when using the default `postgres` user, e.g. when dropping the postgres 7 | # database in restore.sh. Check that something else is used here 8 | if [ "$POSTGRES_USER" == "postgres" ] 9 | then 10 | echo "restoring as the postgres user is not supported, make sure to set the POSTGRES_USER environment variable" 11 | exit 1 12 | fi 13 | 14 | # export the postgres password so that subsequent commands don't ask for it 15 | export PGPASSWORD=$POSTGRES_PASSWORD 16 | 17 | # check that we have an argument for a filename candidate 18 | if [[ $# -eq 0 ]] ; then 19 | echo 'usage:' 20 | echo ' docker-compose run postgres restore ' 21 | echo '' 22 | echo 'to get a list of available backups, run:' 23 | echo ' docker-compose run postgres list-backups' 24 | exit 1 25 | fi 26 | 27 | # set the backupfile variable 28 | BACKUPFILE=/backups/$1 29 | 30 | # check that the file exists 31 | if ! [ -f $BACKUPFILE ]; then 32 | echo "backup file not found" 33 | echo 'to get a list of available backups, run:' 34 | echo ' docker-compose run postgres list-backups' 35 | exit 1 36 | fi 37 | 38 | echo "beginning restore from $1" 39 | echo "-------------------------" 40 | 41 | # delete the db 42 | # deleting the db can fail. Spit out a comment if this happens but continue since the db 43 | # is created in the next step 44 | echo "deleting old database $POSTGRES_USER" 45 | if dropdb -h postgres -U $POSTGRES_USER $POSTGRES_USER 46 | then echo "deleted $POSTGRES_USER database" 47 | else echo "database $POSTGRES_USER does not exist, continue" 48 | fi 49 | 50 | # create a new database 51 | echo "creating new database $POSTGRES_USER" 52 | createdb -h postgres -U $POSTGRES_USER $POSTGRES_USER -O $POSTGRES_USER 53 | 54 | # restore the database 55 | echo "restoring database $POSTGRES_USER" 56 | gunzip -c $BACKUPFILE | psql -h postgres -U $POSTGRES_USER 57 | -------------------------------------------------------------------------------- /config/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OSMCha/osmcha-django/8dfd85bedfc5389113f7e789f3ad9eab2609ca02/config/__init__.py -------------------------------------------------------------------------------- /config/settings/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | -------------------------------------------------------------------------------- /config/settings/aws_production.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | ''' 3 | Production Configurations using AWS ELB 4 | - Use djangosecure 5 | - Use MEMCACHE on Heroku 6 | ''' 7 | from __future__ import absolute_import, unicode_literals 8 | 9 | from .common import * # noqa 10 | 11 | 12 | # SECRET CONFIGURATION 13 | # ------------------------------------------------------------------------------ 14 | # See: https://docs.djangoproject.com/en/dev/ref/settings/#secret-key 15 | # Raises ImproperlyConfigured exception if DJANO_SECRET_KEY not in os.environ 16 | SECRET_KEY = env("DJANGO_SECRET_KEY", default="dont.forget-to-set-the-env-var") 17 | 18 | # This ensures that Django will be able to detect a secure connection 19 | # properly on Heroku. 20 | SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https') 21 | USE_X_FORWARDED_HOST = True 22 | 23 | # gunicorn 24 | # ------------------------------------------------------------------------------ 25 | INSTALLED_APPS += ('gunicorn',) 26 | 27 | 28 | # SITE CONFIGURATION 29 | # ------------------------------------------------------------------------------ 30 | # Hosts/domain names that are valid for this site 31 | # See https://docs.djangoproject.com/en/1.6/ref/settings/#allowed-hosts 32 | ALLOWED_HOSTS = ["*"] 33 | # END SITE CONFIGURATION 34 | 35 | 36 | # TEMPLATE CONFIGURATION 37 | # ------------------------------------------------------------------------------ 38 | # See: https://docs.djangoproject.com/en/dev/ref/templates/api/#django.template.loaders.cached.Loader 39 | TEMPLATES[0]['OPTIONS']['loaders'] = [ 40 | ('django.template.loaders.cached.Loader', [ 41 | 'django.template.loaders.filesystem.Loader', 42 | 'django.template.loaders.app_directories.Loader', 43 | ]), 44 | ] 45 | 46 | REST_FRAMEWORK = { 47 | 'DEFAULT_AUTHENTICATION_CLASSES': ( 48 | 'rest_framework.authentication.TokenAuthentication', 49 | ), 50 | 'DEFAULT_FILTER_BACKENDS': ( 51 | 'django_filters.rest_framework.DjangoFilterBackend', 52 | ), 53 | 'TEST_REQUEST_DEFAULT_FORMAT': 'json', 54 | 'DEFAULT_THROTTLE_CLASSES': ( 55 | 'rest_framework.throttling.AnonRateThrottle', 56 | 'rest_framework.throttling.UserRateThrottle', 57 | ), 58 | 'DEFAULT_THROTTLE_RATES': { 59 | 'anon': env('ANON_USER_THROTTLE_RATE', default='30/min'), 60 | 'user': env('COMMON_USER_THROTTLE_RATE', default='180/min'), 61 | 'non_staff_user': env('NON_STAFF_USER_THROTTLE_RATE', default='3/min') 62 | }, 63 | 'ORDERING_PARAM': 'order_by', 64 | } 65 | -------------------------------------------------------------------------------- /config/settings/local.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | ''' 3 | Local settings 4 | 5 | - Run in Debug mode 6 | - Use console backend for emails 7 | - Add Django Debug Toolbar 8 | - Add django-extensions as app 9 | ''' 10 | 11 | from .common import * # noqa 12 | 13 | # DEBUG 14 | # ------------------------------------------------------------------------------ 15 | DEBUG = env.bool('DJANGO_DEBUG', default=True) 16 | ALLOWED_HOSTS = ['*'] 17 | TEMPLATES[0]['OPTIONS']['debug'] = DEBUG 18 | 19 | # SECRET CONFIGURATION 20 | # ------------------------------------------------------------------------------ 21 | # See: https://docs.djangoproject.com/en/dev/ref/settings/#secret-key 22 | # Note: This key only used for development and testing. 23 | SECRET_KEY = env("DJANGO_SECRET_KEY", default='CHANGEME!!!') 24 | 25 | # Mail settings 26 | # ------------------------------------------------------------------------------ 27 | EMAIL_HOST = 'localhost' 28 | EMAIL_PORT = 1025 29 | EMAIL_BACKEND = env('DJANGO_EMAIL_BACKEND', 30 | default='django.core.mail.backends.console.EmailBackend') 31 | 32 | # CACHING 33 | # ------------------------------------------------------------------------------ 34 | CACHES = { 35 | 'default': { 36 | 'BACKEND': 'django.core.cache.backends.dummy.DummyCache', 37 | 'LOCATION': '' 38 | } 39 | } 40 | 41 | # django-debug-toolbar 42 | # ------------------------------------------------------------------------------ 43 | MIDDLEWARE += ['debug_toolbar.middleware.DebugToolbarMiddleware'] 44 | INSTALLED_APPS += ('debug_toolbar', ) 45 | 46 | INTERNAL_IPS = ('127.0.0.1', '10.0.2.2',) 47 | 48 | DEBUG_TOOLBAR_CONFIG = { 49 | 'DISABLE_PANELS': [ 50 | 'debug_toolbar.panels.redirects.RedirectsPanel', 51 | ], 52 | 'SHOW_TEMPLATE_CONTEXT': True, 53 | } 54 | 55 | # django-extensions 56 | # ------------------------------------------------------------------------------ 57 | INSTALLED_APPS += ('django_extensions', ) 58 | 59 | # TESTING 60 | # ------------------------------------------------------------------------------ 61 | TEST_RUNNER = 'django.test.runner.DiscoverRunner' 62 | 63 | # DATABASE CONFIGURATION 64 | # ------------------------------------------------------------------------------ 65 | # See: https://docs.djangoproject.com/en/dev/ref/settings/#databases 66 | 67 | # Your local stuff: Below this line define 3rd party library settings 68 | -------------------------------------------------------------------------------- /config/settings/tests.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | ''' 3 | Test settings 4 | - Used to run tests fast on the continuous integration server and locally 5 | ''' 6 | 7 | from .common import * # noqa 8 | 9 | 10 | # DEBUG 11 | # ------------------------------------------------------------------------------ 12 | # Turn debug off so tests run faster 13 | DEBUG = False 14 | TEMPLATES[0]['OPTIONS']['debug'] = False 15 | 16 | # SECRET CONFIGURATION 17 | # ------------------------------------------------------------------------------ 18 | # See: https://docs.djangoproject.com/en/dev/ref/settings/#secret-key 19 | # Note: This key only used for development and testing. 20 | SECRET_KEY = env('DJANGO_SECRET_KEY', default='CHANGEME!!!') 21 | 22 | # Mail settings 23 | # ------------------------------------------------------------------------------ 24 | EMAIL_HOST = 'localhost' 25 | EMAIL_PORT = 1025 26 | 27 | # In-memory email backend stores messages in django.core.mail.outbox 28 | # for unit testing purposes 29 | EMAIL_BACKEND = 'django.core.mail.backends.locmem.EmailBackend' 30 | 31 | # CACHING 32 | # ------------------------------------------------------------------------------ 33 | # Speed advantages of in-memory caching without having to run Memcached 34 | CACHES = { 35 | 'default': { 36 | 'BACKEND': 'django.core.cache.backends.locmem.LocMemCache', 37 | 'LOCATION': '' 38 | } 39 | } 40 | 41 | # TESTING 42 | # ------------------------------------------------------------------------------ 43 | TEST_RUNNER = 'django.test.runner.DiscoverRunner' 44 | 45 | 46 | # PASSWORD HASHING 47 | # ------------------------------------------------------------------------------ 48 | # Use fast password hasher so tests run faster 49 | PASSWORD_HASHERS = ( 50 | 'django.contrib.auth.hashers.MD5PasswordHasher', 51 | ) 52 | 53 | # TEMPLATE LOADERS 54 | # ------------------------------------------------------------------------------ 55 | # Keep templates in memory so tests run faster 56 | TEMPLATES[0]['OPTIONS']['loaders'] = [ 57 | ('django.template.loaders.cached.Loader', [ 58 | 'django.template.loaders.filesystem.Loader', 59 | 'django.template.loaders.app_directories.Loader', 60 | ]), 61 | ] 62 | -------------------------------------------------------------------------------- /config/urls.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.conf import settings 5 | from django.urls import include, path, re_path 6 | from django.conf.urls.static import static 7 | from django.contrib import admin 8 | from django.views import defaults 9 | from django.views import static as static_views 10 | from django.http import JsonResponse 11 | from django.shortcuts import redirect 12 | 13 | from rest_framework import permissions 14 | from drf_yasg.views import get_schema_view 15 | from drf_yasg import openapi 16 | 17 | 18 | API_BASE_URL = 'api/v1/' 19 | 20 | urlpatterns = [] 21 | 22 | api_urls = [ 23 | path( 24 | '{}'.format(API_BASE_URL), 25 | include("osmchadjango.changeset.urls", namespace="changeset") 26 | ), 27 | path( 28 | '{}'.format(API_BASE_URL), 29 | include("osmchadjango.supervise.urls", namespace="supervise") 30 | ), 31 | path( 32 | '{}'.format(API_BASE_URL), 33 | include("osmchadjango.users.urls", namespace="users") 34 | ), 35 | path( 36 | '{}'.format(API_BASE_URL), 37 | include("osmchadjango.roulette_integration.urls", namespace="challenge") 38 | ), 39 | ] 40 | 41 | schema_view = get_schema_view( 42 | openapi.Info( 43 | title="OSMCha API", 44 | default_version='v1', 45 | description="OSMCha API Documentation", 46 | contact=openapi.Contact(email="osmcha-dev@openstreetmap.org"), 47 | license=openapi.License(name="Data © OpenStreetMap Contributors, ODBL license"), 48 | ), 49 | public=True, 50 | permission_classes=(permissions.AllowAny,), 51 | ) 52 | 53 | def health_redirect(request): 54 | return redirect(API_BASE_URL + 'health') 55 | 56 | def health_check(request): 57 | return JsonResponse({'status': 'ok'}) 58 | 59 | urlpatterns += [ 60 | # Django Admin 61 | path('admin/', admin.site.urls), 62 | 63 | path('health', health_redirect), 64 | path(API_BASE_URL + 'health', health_check), 65 | 66 | # api docs 67 | re_path( 68 | r'^swagger(?P\.json|\.yaml)$', 69 | schema_view.without_ui(cache_timeout=0), 70 | name='schema-json' 71 | ), 72 | re_path( 73 | r'^api-docs/$', 74 | schema_view.with_ui('swagger', cache_timeout=0), 75 | name='schema-swagger-ui' 76 | ), 77 | 78 | # include api_urls 79 | path( 80 | '', 81 | include(api_urls) 82 | ), 83 | ] 84 | 85 | if settings.DEBUG: 86 | # This allows the error pages to be debugged during development, just visit 87 | # these url in browser to see how these error pages look like. 88 | import debug_toolbar 89 | urlpatterns += [ 90 | path('400/', defaults.bad_request), 91 | path('403/', defaults.permission_denied), 92 | path('404/', defaults.page_not_found), 93 | path('500/', defaults.server_error), 94 | path('__debug__/', include(debug_toolbar.urls)), 95 | ] 96 | -------------------------------------------------------------------------------- /config/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for osmcha-django project. 3 | 4 | This module contains the WSGI application used by Django's development server 5 | and any production WSGI deployments. It should expose a module-level variable 6 | named ``application``. Django's ``runserver`` and ``runfcgi`` commands discover 7 | this application via the ``WSGI_APPLICATION`` setting. 8 | 9 | Usually you will have the standard Django WSGI application here, but it also 10 | might make sense to replace the whole Django WSGI application with a custom one 11 | that later delegates to the Django one. For example, you could introduce WSGI 12 | middleware here, or combine a Django application with an application of another 13 | framework. 14 | 15 | """ 16 | import os 17 | 18 | from django.core.wsgi import get_wsgi_application 19 | 20 | 21 | # We defer to a DJANGO_SETTINGS_MODULE already in the environment. This breaks 22 | # if running multiple sites in the same mod_wsgi process. To fix this, use 23 | # mod_wsgi daemon mode with each site in its own daemon process, or use 24 | # os.environ["DJANGO_SETTINGS_MODULE"] = "config.settings.production" 25 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "config.settings.production") 26 | 27 | # This application object is used by any WSGI server configured to use this 28 | # file. This includes Django's development server, if the WSGI_APPLICATION 29 | # setting points here. 30 | application = get_wsgi_application() 31 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '2' 2 | 3 | services: 4 | postgres: 5 | build: ./compose/postgres 6 | volumes: 7 | - postgres_data:/var/lib/postgresql/data 8 | - postgres_backup:/backups 9 | env_file: .env 10 | 11 | django: 12 | build: 13 | context: . 14 | depends_on: 15 | - postgres 16 | - redis 17 | volumes: 18 | # copy static files into a shared volume so the frontend can serve them 19 | - staticfiles:/app/staticfiles 20 | env_file: .env 21 | 22 | frontend: 23 | image: osmcha-frontend 24 | depends_on: 25 | - django 26 | volumes: 27 | # frontend serves django app's static files via shared volume 28 | - staticfiles:/srv/www/static/django 29 | env_file: .env 30 | 31 | ports: 32 | - "0.0.0.0:8000:80" 33 | 34 | redis: 35 | image: redis:latest 36 | 37 | volumes: 38 | staticfiles: 39 | postgres_data: 40 | postgres_backup: 41 | -------------------------------------------------------------------------------- /docs/__init__.py: -------------------------------------------------------------------------------- 1 | # Included so that Django's startproject comment runs against the docs directory 2 | -------------------------------------------------------------------------------- /docs/deploy.rst: -------------------------------------------------------------------------------- 1 | Deploy 2 | ======== 3 | 4 | This is where you describe how the project is deployed in production. 5 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | .. osmcha-django documentation master file, created by 2 | sphinx-quickstart. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | Welcome to osmcha-django's documentation! 7 | ==================================================================== 8 | 9 | Contents: 10 | 11 | .. toctree:: 12 | :maxdepth: 2 13 | 14 | install 15 | deploy 16 | docker_ec2 17 | tests 18 | 19 | 20 | 21 | Indices and tables 22 | ================== 23 | 24 | * :ref:`genindex` 25 | * :ref:`modindex` 26 | * :ref:`search` 27 | -------------------------------------------------------------------------------- /docs/install.rst: -------------------------------------------------------------------------------- 1 | Install 2 | ========= 3 | 4 | This is where you write how to get a new laptop to run this project. 5 | -------------------------------------------------------------------------------- /env.example: -------------------------------------------------------------------------------- 1 | 2 | # Settings for PostgreSQL client 3 | PGHOST= 4 | PGPORT= 5 | PGUSER= 6 | PGPASSWORD= 7 | PGDATABASE= 8 | 9 | # Settings for PostgreSQL server (should match above) 10 | POSTGRES_USER= 11 | POSTGRES_PASSWORD= 12 | POSTGRES_DB= 13 | 14 | BACKEND_URL=http://django:5000 15 | REDIS_URL=redis://redis:6379 16 | 17 | # General settings 18 | # DJANGO_READ_DOT_ENV_FILE=True 19 | DJANGO_ADMIN_URL= 20 | DJANGO_SETTINGS_MODULE=config.settings.production 21 | DJANGO_SECRET_KEY=P>2<$+5m@>E(K(GGt-i!X,VjZ" 16 | echo "available commands are:" 17 | echo -e "\tlist\t\tPrint a list of all packages defined on ${OS_REQUIREMENTS_FILENAME} file" 18 | echo -e "\thelp\t\tPrint this help" 19 | echo -e "\n\tCommands that require superuser permission:" 20 | echo -e "\tinstall\t\tInstall packages defined on ${OS_REQUIREMENTS_FILENAME} file. Note: This\n\t\t\t does not upgrade the packages already installed for new\n\t\t\t versions, even if new version is available in the repository." 21 | echo -e "\tupgrade\t\tSame that install, but upgrate the already installed packages,\n\t\t\t if new version is available." 22 | 23 | } 24 | 25 | # Read the requirements.apt file, and remove comments and blank lines 26 | function list_packages(){ 27 | cat ${OS_REQUIREMENTS_FILENAME} | grep -v "#" | grep -v "^$"; 28 | } 29 | 30 | function install() 31 | { 32 | list_packages | xargs apt-get --no-upgrade install -y; 33 | } 34 | 35 | function upgrade() 36 | { 37 | list_packages | xargs apt-get install -y; 38 | } 39 | 40 | 41 | function install_or_upgrade() 42 | { 43 | P=${1} 44 | PARAN=${P:-"install"} 45 | 46 | if [[ $EUID -ne 0 ]]; then 47 | echo -e "\nYou must run this with root privilege" 2>&1 48 | echo -e "Please do:\n" 2>&1 49 | echo "sudo ./${0##*/} $PARAN" 2>&1 50 | echo -e "\n" 2>&1 51 | 52 | exit 1 53 | else 54 | 55 | apt-get update 56 | 57 | # Install the basic compilation dependencies and other required libraries of this project 58 | if [ "$PARAN" == "install" ]; then 59 | install; 60 | else 61 | upgrade; 62 | fi 63 | 64 | # cleaning downloaded packages from apt-get cache 65 | apt-get clean 66 | 67 | exit 0 68 | fi 69 | 70 | 71 | } 72 | 73 | 74 | # Handle command argument 75 | case "$1" in 76 | install) install_or_upgrade;; 77 | upgrade) install_or_upgrade "upgrade";; 78 | list) list_packages;; 79 | help) usage_message;; 80 | *) wrong_command $1;; 81 | esac 82 | 83 | -------------------------------------------------------------------------------- /install_python_dependencies.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | pip --version >/dev/null 2>&1 || { 4 | echo >&2 -e "\npip is required but it's not installed." 5 | echo >&2 -e "You can install it by running the following command:\n" 6 | echo >&2 "wget https://bootstrap.pypa.io/get-pip.py; chmod +x get-pip.py; sudo ./get-pip.py" 7 | echo >&2 -e "\n" 8 | echo >&2 -e "\nFor more information, see pip documentation: https://pip.pypa.io/en/latest/" 9 | exit 1; 10 | } 11 | 12 | virtualenv --version >/dev/null 2>&1 || { 13 | echo >&2 -e "\nvirtualenv is required but it's not installed." 14 | echo >&2 -e "You can install it by running the following command:\n" 15 | echo >&2 "sudo pip install virtualenv" 16 | echo >&2 -e "\n" 17 | echo >&2 -e "\nFor more information, see virtualenv documentation: https://virtualenv.pypa.io/en/latest/" 18 | exit 1; 19 | } 20 | 21 | if [ -z "$VIRTUAL_ENV" ]; then 22 | echo >&2 -e "\nYou need activate a virtualenv first" 23 | echo >&2 -e 'If you do not have a virtualenv created, run the following command to create and automatically activate a new virtualenv named "venv" on current folder:\n' 24 | echo >&2 -e "virtualenv venv" 25 | echo >&2 -e "\nTo leave/disable the currently active virtualenv, run the following command:\n" 26 | echo >&2 "deactivate" 27 | echo >&2 -e "\nTo activate the virtualenv again, run the following command:\n" 28 | echo >&2 "source venv/bin/activate" 29 | echo >&2 -e "\nFor more information, see virtualenv documentation: https://virtualenv.pypa.io/en/latest/" 30 | echo >&2 -e "\n" 31 | exit 1; 32 | else 33 | 34 | pip install -r requirements/local.txt 35 | pip install -r requirements/test.txt 36 | pip install -r requirements.txt 37 | fi 38 | -------------------------------------------------------------------------------- /manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import os 3 | import sys 4 | 5 | if __name__ == "__main__": 6 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "config.settings.local") 7 | 8 | from django.core.management import execute_from_command_line 9 | 10 | execute_from_command_line(sys.argv) 11 | -------------------------------------------------------------------------------- /osmchadjango/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | __version__ = '0.1.0' 3 | __version_info__ = tuple([int(num) if num.isdigit() else num for num in __version__.replace('-', '.', 1).split('.')]) 4 | -------------------------------------------------------------------------------- /osmchadjango/changeset/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OSMCha/osmcha-django/8dfd85bedfc5389113f7e789f3ad9eab2609ca02/osmchadjango/changeset/__init__.py -------------------------------------------------------------------------------- /osmchadjango/changeset/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib.gis import admin 2 | 3 | from .models import Changeset, SuspicionReasons, Tag 4 | 5 | 6 | class ChangesetAdmin(admin.OSMGeoAdmin): 7 | search_fields = ['id'] 8 | list_display = ['id', 'user', 'create', 'modify', 'delete', 'checked', 9 | 'date', 'check_user'] 10 | list_filter = ['checked', 'is_suspect', 'reasons'] 11 | date_hierarchy = 'date' 12 | 13 | 14 | class ReasonsAdmin(admin.ModelAdmin): 15 | search_fields = ['name'] 16 | list_display = ['name', 'is_visible', 'for_changeset', 'for_feature'] 17 | list_filter = ['is_visible', 'for_changeset', 'for_feature'] 18 | 19 | 20 | admin.site.register(Changeset, ChangesetAdmin) 21 | admin.site.register(SuspicionReasons, ReasonsAdmin) 22 | admin.site.register(Tag, ReasonsAdmin) 23 | -------------------------------------------------------------------------------- /osmchadjango/changeset/fixtures/brazil.geojson: -------------------------------------------------------------------------------- 1 | { 2 | "type": "FeatureCollection", 3 | "features": [ 4 | { 5 | "type": "Feature", 6 | "properties": {}, 7 | "geometry": { 8 | "type": "Polygon", 9 | "coordinates": [ 10 | [ 11 | [ 12 | -48.9990234375, 13 | 0.615222552406841 14 | ], 15 | [ 16 | -39.92431640625, 17 | -2.493109378566158 18 | ], 19 | [ 20 | -31.772460937500004, 21 | -3.524386660147952 22 | ], 23 | [ 24 | -34.60693359375, 25 | -8.276727101164033 26 | ], 27 | [ 28 | -36.36474609375, 29 | -10.714586690981497 30 | ], 31 | [ 32 | -38.60595703125, 33 | -13.581920900545844 34 | ], 35 | [ 36 | -38.8916015625, 37 | -17.916022703877665 38 | ], 39 | [ 40 | -40.3857421875, 41 | -21.53484700204879 42 | ], 43 | [ 44 | -41.473388671875, 45 | -23.433009077420344 46 | ], 47 | [ 48 | -46.6259765625, 49 | -24.497146320571886 50 | ], 51 | [ 52 | -48.0322265625, 53 | -26.175158990178122 54 | ], 55 | [ 56 | -48.33984375, 57 | -29.075375179558346 58 | ], 59 | [ 60 | -53.28369140625, 61 | -34.28899186503752 62 | ], 63 | [ 64 | -53.96484375, 65 | -33.26624989076273 66 | ], 67 | [ 68 | -57.89794921874999, 69 | -30.202113679097216 70 | ], 71 | [ 72 | -54.66796875, 73 | -26.549222577692028 74 | ], 75 | [ 76 | -58.35937499999999, 77 | -22.06527806776582 78 | ], 79 | [ 80 | -59.23828124999999, 81 | -17.5602465032949 82 | ], 83 | [ 84 | -66.9287109375, 85 | -11.005904459659451 86 | ], 87 | [ 88 | -70.5322265625, 89 | -11.39387923296741 90 | ], 91 | [ 92 | -74.4873046875, 93 | -8.450638800330989 94 | ], 95 | [ 96 | -73.3447265625, 97 | -4.609278084409823 98 | ], 99 | [ 100 | -70.2685546875, 101 | 2.2406396093827334 102 | ], 103 | [ 104 | -64.599609375, 105 | 4.915832801313164 106 | ], 107 | [ 108 | -59.67773437500001, 109 | 5.747174076651375 110 | ], 111 | [ 112 | -58.27148437499999, 113 | 2.986927393334876 114 | ], 115 | [ 116 | -51.064453125, 117 | 5.441022303717974 118 | ], 119 | [ 120 | -48.9990234375, 121 | 0.615222552406841 122 | ] 123 | ] 124 | ] 125 | } 126 | } 127 | ] 128 | } -------------------------------------------------------------------------------- /osmchadjango/changeset/management/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OSMCha/osmcha-django/8dfd85bedfc5389113f7e789f3ad9eab2609ca02/osmchadjango/changeset/management/__init__.py -------------------------------------------------------------------------------- /osmchadjango/changeset/management/commands/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OSMCha/osmcha-django/8dfd85bedfc5389113f7e789f3ad9eab2609ca02/osmchadjango/changeset/management/commands/__init__.py -------------------------------------------------------------------------------- /osmchadjango/changeset/management/commands/backfill_changesets.py: -------------------------------------------------------------------------------- 1 | from datetime import date, datetime, timedelta 2 | 3 | from django.core.management.base import BaseCommand 4 | 5 | from ...models import Changeset 6 | from ...tasks import create_changeset 7 | 8 | 9 | class Command(BaseCommand): 10 | help = """Backfill missing changesets in a date range. 11 | Start and end dates should be in YYYY-MM-DD format.""" 12 | 13 | def add_arguments(self, parser): 14 | parser.add_argument("--start_date", type=str) 15 | parser.add_argument("--end_date", type=str) 16 | 17 | def handle(self, *args, **options): 18 | # if start_date is not defined, set it as yesterday 19 | try: 20 | start_date = date.fromisoformat(options["start_date"]) 21 | except (ValueError, TypeError): 22 | start_date = date.today() - timedelta(days=1) 23 | # if end_date is not defined, set it as today 24 | try: 25 | end_date = date.fromisoformat(options["end_date"]) 26 | except (ValueError, TypeError): 27 | end_date = datetime.now() 28 | 29 | cl = Changeset.objects.filter( 30 | date__gte=start_date, date__lte=end_date 31 | ).values_list("id") 32 | cl = [c[0] for c in cl] 33 | 34 | id = cl[len(cl) - 1] 35 | final = cl[0] 36 | while id < final: 37 | if id not in cl: 38 | try: 39 | create_changeset(id) 40 | except Exception as e: 41 | self.stdout.write("Failed to import changeset {}: {}".format(id, e)) 42 | id = id + 1 43 | -------------------------------------------------------------------------------- /osmchadjango/changeset/management/commands/delete_old_data.py: -------------------------------------------------------------------------------- 1 | from django.core.management.base import BaseCommand 2 | from django.utils import timezone 3 | 4 | from ...models import Changeset 5 | 6 | 7 | def get_six_months_ago(): 8 | return timezone.now() - timezone.timedelta(days=180) 9 | 10 | 11 | class Command(BaseCommand): 12 | help = """Command that deletes all the unchecked changesets older than 180 days. 13 | """ 14 | 15 | def handle(self, *args, **options): 16 | date = get_six_months_ago 17 | Changeset.objects.filter(date__lt=date(), checked=False).delete() 18 | -------------------------------------------------------------------------------- /osmchadjango/changeset/management/commands/fetchchangesets.py: -------------------------------------------------------------------------------- 1 | from django.core.management.base import BaseCommand 2 | 3 | from ...tasks import fetch_latest 4 | 5 | 6 | class Command(BaseCommand): 7 | help = """Command to import all the replication files since the last import 8 | or the last 1000.""" 9 | 10 | def handle(self, *args, **options): 11 | fetch_latest() 12 | -------------------------------------------------------------------------------- /osmchadjango/changeset/management/commands/import_file.py: -------------------------------------------------------------------------------- 1 | from django.core.management.base import BaseCommand 2 | from django.conf import settings 3 | 4 | from osmcha.changeset import ChangesetList 5 | 6 | from ...tasks import create_changeset 7 | 8 | 9 | class Command(BaseCommand): 10 | help = """Read a local file and import all changesets.""" 11 | 12 | def add_arguments(self, parser): 13 | parser.add_argument('filename', nargs=1, type=str) 14 | 15 | def handle(self, *args, **options): 16 | filename = options['filename'][0] 17 | cl = ChangesetList(filename, geojson=settings.CHANGESETS_FILTER) 18 | imported = [] 19 | 20 | for c in cl.changesets: 21 | try: 22 | create_changeset(c['id']) 23 | imported.append(c['id']) 24 | except Exception as e: 25 | self.stdout.write("Failed to import changeset {}: {}".format(c["id"], e)) 26 | 27 | self.stdout.write( 28 | '{} changesets created from {}.'.format(len(imported), filename) 29 | ) 30 | -------------------------------------------------------------------------------- /osmchadjango/changeset/management/commands/mark_harmful_changeset.py: -------------------------------------------------------------------------------- 1 | from django.core.management.base import BaseCommand 2 | from django.utils import timezone 3 | 4 | from ...models import Changeset 5 | from ....users.models import User 6 | 7 | 8 | class Command(BaseCommand): 9 | help = 'Marks a list of changesets as harmful' 10 | 11 | def add_arguments(self, parser): 12 | parser.add_argument('check_username', nargs='+', type=str) 13 | parser.add_argument('filename', nargs='+', type=str) 14 | 15 | def handle(self, *args, **options): 16 | check_username = options['check_username'][0] 17 | check_user = User.objects.filter(username=check_username) 18 | if check_user: 19 | check_user = check_user[0] 20 | filename = options['filename'][0] 21 | fr = open(filename, 'r') 22 | for line in fr: 23 | changeset_id = line.rstrip() 24 | try: 25 | changeset = Changeset.objects.get(id=changeset_id) 26 | changeset.checked = True 27 | changeset.harmful = True 28 | changeset.check_user = check_user 29 | changeset.check_date = timezone.now() 30 | changeset.save() 31 | except: 32 | pass 33 | fr.close() 34 | -------------------------------------------------------------------------------- /osmchadjango/changeset/management/commands/merge_reasons.py: -------------------------------------------------------------------------------- 1 | from django.core.management.base import BaseCommand 2 | from django.conf import settings 3 | from django.db import connection 4 | 5 | from ...models import SuspicionReasons 6 | 7 | 8 | class Command(BaseCommand): 9 | help = """Merge two SuspicionReasons. All the changesets associated with the 10 | first Reason will be moved to the second one and the first 11 | SuspicionReason will be deleted. 12 | """ 13 | 14 | def add_arguments(self, parser): 15 | parser.add_argument('reason_1', nargs=1, type=str) 16 | parser.add_argument('reason_2', nargs=1, type=str) 17 | 18 | def handle(self, *args, **options): 19 | try: 20 | origin_reason = SuspicionReasons.objects.get( 21 | id=options['reason_1'][0] 22 | ) 23 | final_reason = SuspicionReasons.objects.get( 24 | id=options['reason_2'][0] 25 | ) 26 | changesets = origin_reason.changesets.exclude(reasons=final_reason) 27 | excluded_changesets = final_reason.changesets.filter( 28 | reasons=final_reason 29 | ) 30 | changeset_number = changesets.count() 31 | origin_reason_name = origin_reason.name 32 | with connection.cursor() as cursor: 33 | cursor.execute( 34 | """UPDATE changeset_changeset_reasons 35 | SET suspicionreasons_id=%s 36 | WHERE suspicionreasons_id=%s AND changeset_id not in %s 37 | """, 38 | [ 39 | final_reason.id, 40 | origin_reason.id, 41 | tuple([c.id for c in excluded_changesets]) 42 | ] 43 | ) 44 | origin_reason.delete() 45 | self.stdout.write( 46 | """{} changesets were moved from '{}' to '{}' SuspicionReasons. 47 | '{}' has been successfully deleted. 48 | """.format( 49 | changeset_number, origin_reason_name, final_reason.name, 50 | origin_reason_name 51 | ) 52 | ) 53 | except SuspicionReasons.DoesNotExist: 54 | self.stdout.write( 55 | """Verify the SuspicionReasons ids. 56 | One or both of them does not exist. 57 | """ 58 | ) 59 | -------------------------------------------------------------------------------- /osmchadjango/changeset/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import models, migrations 5 | import django.contrib.gis.db.models.fields 6 | from django.conf import settings 7 | 8 | 9 | class Migration(migrations.Migration): 10 | 11 | dependencies = [ 12 | migrations.swappable_dependency(settings.AUTH_USER_MODEL), 13 | ] 14 | 15 | operations = [ 16 | migrations.CreateModel( 17 | name='Changeset', 18 | fields=[ 19 | ('id', models.AutoField(serialize=False, primary_key=True, auto_created=True, verbose_name='ID')), 20 | ('user', models.CharField(max_length=1000)), 21 | ('editor', models.CharField(max_length=255)), 22 | ('comment', models.CharField(max_length=255, blank=True)), 23 | ('date', models.DateTimeField()), 24 | ('created', models.IntegerField()), 25 | ('modified', models.IntegerField()), 26 | ('deleted', models.IntegerField()), 27 | ('bbox', django.contrib.gis.db.models.fields.PolygonField(srid=4326)), 28 | ('harmful', models.NullBooleanField()), 29 | ('checked', models.BooleanField(default=False)), 30 | ('check_date', models.DateTimeField(null=True, blank=True)), 31 | ('check_user', models.ForeignKey(to=settings.AUTH_USER_MODEL, on_delete=models.deletion.SET_NULL, blank=True)), 32 | ], 33 | ), 34 | migrations.CreateModel( 35 | name='SuspicionReasons', 36 | fields=[ 37 | ('id', models.AutoField(serialize=False, primary_key=True, auto_created=True, verbose_name='ID')), 38 | ('name', models.CharField(max_length=255)), 39 | ], 40 | ), 41 | migrations.AddField( 42 | model_name='changeset', 43 | name='reasons', 44 | field=models.ManyToManyField(to='changeset.SuspicionReasons'), 45 | ), 46 | ] 47 | -------------------------------------------------------------------------------- /osmchadjango/changeset/migrations/0002_auto_20150804_0119.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import models, migrations 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('changeset', '0001_initial'), 11 | ] 12 | 13 | operations = [ 14 | migrations.AlterField( 15 | model_name='changeset', 16 | name='reasons', 17 | field=models.ManyToManyField(related_name='changesets', to='changeset.SuspicionReasons'), 18 | ), 19 | ] 20 | -------------------------------------------------------------------------------- /osmchadjango/changeset/migrations/0003_auto_20150804_0126.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import models, migrations 5 | from django.conf import settings 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('changeset', '0002_auto_20150804_0119'), 12 | ] 13 | 14 | operations = [ 15 | migrations.AlterField( 16 | model_name='changeset', 17 | name='check_user', 18 | field=models.ForeignKey(blank=True, to=settings.AUTH_USER_MODEL, on_delete=models.deletion.SET_NULL, null=True), 19 | ), 20 | ] 21 | -------------------------------------------------------------------------------- /osmchadjango/changeset/migrations/0004_auto_20150828_0128.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import models, migrations 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('changeset', '0003_auto_20150804_0126'), 11 | ] 12 | 13 | operations = [ 14 | migrations.AddField( 15 | model_name='changeset', 16 | name='powerfull_editor', 17 | field=models.BooleanField(default=False), 18 | ), 19 | migrations.AddField( 20 | model_name='changeset', 21 | name='source', 22 | field=models.CharField(max_length=1000, blank=True), 23 | ), 24 | migrations.AlterField( 25 | model_name='changeset', 26 | name='comment', 27 | field=models.CharField(max_length=1000, blank=True), 28 | ), 29 | ] 30 | -------------------------------------------------------------------------------- /osmchadjango/changeset/migrations/0005_auto_20150828_1942.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import models, migrations 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('changeset', '0004_auto_20150828_0128'), 11 | ] 12 | 13 | operations = [ 14 | migrations.RenameField( 15 | model_name='changeset', 16 | old_name='created', 17 | new_name='create', 18 | ), 19 | migrations.RenameField( 20 | model_name='changeset', 21 | old_name='deleted', 22 | new_name='delete', 23 | ), 24 | migrations.RenameField( 25 | model_name='changeset', 26 | old_name='modified', 27 | new_name='modify', 28 | ), 29 | ] 30 | -------------------------------------------------------------------------------- /osmchadjango/changeset/migrations/0006_changeset_imagery_used.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import models, migrations 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('changeset', '0005_auto_20150828_1942'), 11 | ] 12 | 13 | operations = [ 14 | migrations.AddField( 15 | model_name='changeset', 16 | name='imagery_used', 17 | field=models.CharField(max_length=1000, blank=True), 18 | ), 19 | ] 20 | -------------------------------------------------------------------------------- /osmchadjango/changeset/migrations/0007_auto_20150828_1947.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import models, migrations 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('changeset', '0006_changeset_imagery_used'), 11 | ] 12 | 13 | operations = [ 14 | migrations.RemoveField( 15 | model_name='changeset', 16 | name='harmful', 17 | ), 18 | migrations.AddField( 19 | model_name='changeset', 20 | name='is_suspect', 21 | field=models.BooleanField(default=True), 22 | preserve_default=False, 23 | ), 24 | ] 25 | -------------------------------------------------------------------------------- /osmchadjango/changeset/migrations/0008_changeset_uid.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import migrations, models 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('changeset', '0007_auto_20150828_1947'), 11 | ] 12 | 13 | operations = [ 14 | migrations.AddField( 15 | model_name='changeset', 16 | name='uid', 17 | field=models.CharField(default='00', verbose_name='User ID', max_length=255), 18 | preserve_default=False, 19 | ), 20 | ] 21 | -------------------------------------------------------------------------------- /osmchadjango/changeset/migrations/0009_auto_20151226_0012.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import migrations, models 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('changeset', '0008_changeset_uid'), 11 | ] 12 | 13 | operations = [ 14 | migrations.AddField( 15 | model_name='changeset', 16 | name='harmfull', 17 | field=models.NullBooleanField(), 18 | ), 19 | migrations.AlterField( 20 | model_name='changeset', 21 | name='powerfull_editor', 22 | field=models.BooleanField(default=False, verbose_name='Powerfull Editor'), 23 | ), 24 | ] 25 | -------------------------------------------------------------------------------- /osmchadjango/changeset/migrations/0010_auto_20151227_1306.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import migrations, models 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('changeset', '0009_auto_20151226_0012'), 11 | ] 12 | 13 | operations = [ 14 | migrations.RenameField( 15 | model_name='changeset', 16 | old_name='harmfull', 17 | new_name='harmful', 18 | ), 19 | ] 20 | -------------------------------------------------------------------------------- /osmchadjango/changeset/migrations/0011_import.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import migrations, models 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('changeset', '0010_auto_20151227_1306'), 11 | ] 12 | 13 | operations = [ 14 | migrations.CreateModel( 15 | name='Import', 16 | fields=[ 17 | ('id', models.AutoField(verbose_name='ID', serialize=False, primary_key=True, auto_created=True)), 18 | ('start', models.IntegerField()), 19 | ('end', models.IntegerField()), 20 | ('date', models.DateTimeField(verbose_name='Date of the import', auto_now_add=True)), 21 | ], 22 | ), 23 | ] 24 | -------------------------------------------------------------------------------- /osmchadjango/changeset/migrations/0012_auto_20160125_1019.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import migrations, models 5 | from django.conf import settings 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | migrations.swappable_dependency(settings.AUTH_USER_MODEL), 12 | ('changeset', '0011_import'), 13 | ] 14 | 15 | operations = [ 16 | migrations.CreateModel( 17 | name='UserWhitelist', 18 | fields=[ 19 | ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), 20 | ('whitelist_user', models.CharField(max_length=1000)), 21 | ('user', models.ForeignKey(to=settings.AUTH_USER_MODEL, on_delete=models.deletion.SET_NULL)), 22 | ], 23 | ), 24 | migrations.AlterUniqueTogether( 25 | name='userwhitelist', 26 | unique_together=set([('user', 'whitelist_user')]), 27 | ), 28 | ] 29 | -------------------------------------------------------------------------------- /osmchadjango/changeset/migrations/0013_auto_20160125_1021.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import migrations, models 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('changeset', '0012_auto_20160125_1019'), 11 | ] 12 | 13 | operations = [ 14 | migrations.AlterField( 15 | model_name='changeset', 16 | name='user', 17 | field=models.CharField(max_length=1000, db_index=True), 18 | ), 19 | ] 20 | -------------------------------------------------------------------------------- /osmchadjango/changeset/migrations/0014_auto_20160216_1029.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import migrations, models 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('changeset', '0013_auto_20160125_1021'), 11 | ] 12 | 13 | operations = [ 14 | migrations.CreateModel( 15 | name='UserDetail', 16 | fields=[ 17 | ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), 18 | ('name', models.CharField(max_length=1000)), 19 | ('blocks', models.IntegerField()), 20 | ], 21 | ), 22 | migrations.AddField( 23 | model_name='changeset', 24 | name='user_detail', 25 | field=models.ForeignKey(blank=True, to='changeset.UserDetail', on_delete=models.deletion.SET_NULL, null=True), 26 | ), 27 | ] 28 | -------------------------------------------------------------------------------- /osmchadjango/changeset/migrations/0015_auto_20160217_0511.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import migrations, models 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('changeset', '0014_auto_20160216_1029'), 11 | ] 12 | 13 | operations = [ 14 | migrations.AlterField( 15 | model_name='userdetail', 16 | name='name', 17 | field=models.CharField(unique=True, max_length=1000), 18 | ), 19 | ] 20 | -------------------------------------------------------------------------------- /osmchadjango/changeset/migrations/0016_auto_20160217_1004.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import migrations, models 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('changeset', '0015_auto_20160217_0511'), 11 | ] 12 | 13 | operations = [ 14 | migrations.AddField( 15 | model_name='userdetail', 16 | name='no', 17 | field=models.IntegerField(help_text='Number of Changesets', null=True, blank=True), 18 | ), 19 | migrations.AddField( 20 | model_name='userdetail', 21 | name='since', 22 | field=models.DateTimeField(help_text='Mapper since', null=True, blank=True), 23 | ), 24 | ] 25 | -------------------------------------------------------------------------------- /osmchadjango/changeset/migrations/0017_auto_20160217_1102.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import migrations, models 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('changeset', '0016_auto_20160217_1004'), 11 | ] 12 | 13 | operations = [ 14 | migrations.RenameField( 15 | model_name='userdetail', 16 | old_name='blocks', 17 | new_name='contributor_blocks', 18 | ), 19 | migrations.RenameField( 20 | model_name='userdetail', 21 | old_name='name', 22 | new_name='contributor_name', 23 | ), 24 | migrations.RemoveField( 25 | model_name='userdetail', 26 | name='no', 27 | ), 28 | migrations.RemoveField( 29 | model_name='userdetail', 30 | name='since', 31 | ), 32 | migrations.AddField( 33 | model_name='userdetail', 34 | name='changeset_no', 35 | field=models.IntegerField(help_text='Number of changesets', null=True, blank=True), 36 | ), 37 | migrations.AddField( 38 | model_name='userdetail', 39 | name='contributor_since', 40 | field=models.DateTimeField(null=True, blank=True), 41 | ), 42 | ] 43 | -------------------------------------------------------------------------------- /osmchadjango/changeset/migrations/0018_auto_20160218_0218.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import migrations, models 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('changeset', '0017_auto_20160217_1102'), 11 | ] 12 | 13 | operations = [ 14 | migrations.RenameField( 15 | model_name='userdetail', 16 | old_name='changeset_no', 17 | new_name='changesets_no', 18 | ), 19 | migrations.AddField( 20 | model_name='userdetail', 21 | name='changesets_changes', 22 | field=models.IntegerField(null=True, blank=True), 23 | ), 24 | migrations.AddField( 25 | model_name='userdetail', 26 | name='changesets_f_tstamp', 27 | field=models.DateTimeField(null=True, blank=True), 28 | ), 29 | migrations.AddField( 30 | model_name='userdetail', 31 | name='changesets_l_tstamp', 32 | field=models.DateTimeField(null=True, blank=True), 33 | ), 34 | migrations.AddField( 35 | model_name='userdetail', 36 | name='changesets_mapping_days', 37 | field=models.CharField(default='', max_length=128), 38 | preserve_default=False, 39 | ), 40 | migrations.AddField( 41 | model_name='userdetail', 42 | name='contributor_traces', 43 | field=models.IntegerField(null=True, blank=True), 44 | ), 45 | migrations.AddField( 46 | model_name='userdetail', 47 | name='nodes_c', 48 | field=models.IntegerField(null=True, blank=True), 49 | ), 50 | migrations.AddField( 51 | model_name='userdetail', 52 | name='nodes_d', 53 | field=models.IntegerField(null=True, blank=True), 54 | ), 55 | migrations.AddField( 56 | model_name='userdetail', 57 | name='nodes_m', 58 | field=models.IntegerField(null=True, blank=True), 59 | ), 60 | migrations.AddField( 61 | model_name='userdetail', 62 | name='relations_c', 63 | field=models.IntegerField(null=True, blank=True), 64 | ), 65 | migrations.AddField( 66 | model_name='userdetail', 67 | name='relations_d', 68 | field=models.IntegerField(null=True, blank=True), 69 | ), 70 | migrations.AddField( 71 | model_name='userdetail', 72 | name='relations_m', 73 | field=models.IntegerField(null=True, blank=True), 74 | ), 75 | migrations.AddField( 76 | model_name='userdetail', 77 | name='ways_c', 78 | field=models.IntegerField(null=True, blank=True), 79 | ), 80 | migrations.AddField( 81 | model_name='userdetail', 82 | name='ways_d', 83 | field=models.IntegerField(null=True, blank=True), 84 | ), 85 | migrations.AddField( 86 | model_name='userdetail', 87 | name='ways_m', 88 | field=models.IntegerField(null=True, blank=True), 89 | ), 90 | ] 91 | -------------------------------------------------------------------------------- /osmchadjango/changeset/migrations/0019_auto_20160222_0726.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import migrations, models 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('changeset', '0018_auto_20160218_0218'), 11 | ] 12 | 13 | operations = [ 14 | migrations.AlterField( 15 | model_name='changeset', 16 | name='create', 17 | field=models.IntegerField(db_index=True), 18 | ), 19 | migrations.AlterField( 20 | model_name='changeset', 21 | name='delete', 22 | field=models.IntegerField(db_index=True), 23 | ), 24 | migrations.AlterField( 25 | model_name='changeset', 26 | name='is_suspect', 27 | field=models.BooleanField(db_index=True), 28 | ), 29 | migrations.AlterField( 30 | model_name='changeset', 31 | name='modify', 32 | field=models.IntegerField(db_index=True), 33 | ), 34 | migrations.AlterField( 35 | model_name='userdetail', 36 | name='changesets_changes', 37 | field=models.IntegerField(db_index=True, null=True, blank=True), 38 | ), 39 | migrations.AlterField( 40 | model_name='userdetail', 41 | name='changesets_no', 42 | field=models.IntegerField(help_text='Number of changesets', null=True, db_index=True, blank=True), 43 | ), 44 | ] 45 | -------------------------------------------------------------------------------- /osmchadjango/changeset/migrations/0020_auto_20160222_2342.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import migrations, models 5 | import django.contrib.gis.db.models.fields 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('changeset', '0019_auto_20160222_0726'), 12 | ] 13 | 14 | operations = [ 15 | migrations.AlterField( 16 | model_name='changeset', 17 | name='bbox', 18 | field=django.contrib.gis.db.models.fields.PolygonField(srid=4326, blank=True), 19 | ), 20 | migrations.AlterField( 21 | model_name='changeset', 22 | name='create', 23 | field=models.IntegerField(db_index=True, blank=True), 24 | ), 25 | migrations.AlterField( 26 | model_name='changeset', 27 | name='date', 28 | field=models.DateTimeField(blank=True), 29 | ), 30 | migrations.AlterField( 31 | model_name='changeset', 32 | name='delete', 33 | field=models.IntegerField(db_index=True, blank=True), 34 | ), 35 | migrations.AlterField( 36 | model_name='changeset', 37 | name='editor', 38 | field=models.CharField(max_length=255, blank=True), 39 | ), 40 | migrations.AlterField( 41 | model_name='changeset', 42 | name='modify', 43 | field=models.IntegerField(db_index=True, blank=True), 44 | ), 45 | ] 46 | -------------------------------------------------------------------------------- /osmchadjango/changeset/migrations/0021_auto_20160222_2345.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import migrations, models 5 | import django.contrib.gis.db.models.fields 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('changeset', '0020_auto_20160222_2342'), 12 | ] 13 | 14 | operations = [ 15 | migrations.AlterField( 16 | model_name='changeset', 17 | name='bbox', 18 | field=django.contrib.gis.db.models.fields.PolygonField(srid=4326, null=True), 19 | ), 20 | migrations.AlterField( 21 | model_name='changeset', 22 | name='comment', 23 | field=models.CharField(max_length=1000, null=True, blank=True), 24 | ), 25 | migrations.AlterField( 26 | model_name='changeset', 27 | name='create', 28 | field=models.IntegerField(null=True, db_index=True), 29 | ), 30 | migrations.AlterField( 31 | model_name='changeset', 32 | name='date', 33 | field=models.DateTimeField(null=True), 34 | ), 35 | migrations.AlterField( 36 | model_name='changeset', 37 | name='delete', 38 | field=models.IntegerField(null=True, db_index=True), 39 | ), 40 | migrations.AlterField( 41 | model_name='changeset', 42 | name='editor', 43 | field=models.CharField(max_length=255, null=True, blank=True), 44 | ), 45 | migrations.AlterField( 46 | model_name='changeset', 47 | name='imagery_used', 48 | field=models.CharField(max_length=1000, null=True, blank=True), 49 | ), 50 | migrations.AlterField( 51 | model_name='changeset', 52 | name='modify', 53 | field=models.IntegerField(null=True, db_index=True), 54 | ), 55 | migrations.AlterField( 56 | model_name='changeset', 57 | name='source', 58 | field=models.CharField(max_length=1000, null=True, blank=True), 59 | ), 60 | ] 61 | -------------------------------------------------------------------------------- /osmchadjango/changeset/migrations/0022_auto_20160222_2358.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import migrations, models 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('changeset', '0021_auto_20160222_2345'), 11 | ] 12 | 13 | operations = [ 14 | migrations.AlterField( 15 | model_name='suspicionreasons', 16 | name='name', 17 | field=models.CharField(max_length=255, db_index=True), 18 | ), 19 | ] 20 | -------------------------------------------------------------------------------- /osmchadjango/changeset/migrations/0023_userdetail_contributor_uid.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import migrations, models 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('changeset', '0022_auto_20160222_2358'), 11 | ] 12 | 13 | operations = [ 14 | migrations.AddField( 15 | model_name='userdetail', 16 | name='contributor_uid', 17 | field=models.IntegerField(db_index=True, null=True, blank=True), 18 | ), 19 | ] 20 | -------------------------------------------------------------------------------- /osmchadjango/changeset/migrations/0024_auto_20160226_0738.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import migrations, models 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('changeset', '0023_userdetail_contributor_uid'), 11 | ] 12 | 13 | operations = [ 14 | migrations.AlterField( 15 | model_name='userdetail', 16 | name='changesets_mapping_days', 17 | field=models.CharField(max_length=128, null=True, blank=True), 18 | ), 19 | ] 20 | -------------------------------------------------------------------------------- /osmchadjango/changeset/migrations/0025_suspiciousfeature.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import migrations, models 5 | import django.contrib.gis.db.models.fields 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('changeset', '0024_auto_20160226_0738'), 12 | ] 13 | 14 | operations = [ 15 | migrations.CreateModel( 16 | name='SuspiciousFeature', 17 | fields=[ 18 | ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), 19 | ('score', models.IntegerField(null=True, blank=True)), 20 | ('osm_id', models.IntegerField()), 21 | ('geometry', django.contrib.gis.db.models.fields.GeometryField(srid=4326)), 22 | ('geojson', models.TextField()), 23 | ('changeset', models.ForeignKey(to='changeset.Changeset', on_delete=models.deletion.CASCADE)), 24 | ('reasons', models.ManyToManyField(to='changeset.SuspicionReasons')), 25 | ], 26 | ), 27 | ] 28 | -------------------------------------------------------------------------------- /osmchadjango/changeset/migrations/0026_auto_20160314_1227.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import migrations, models 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('changeset', '0025_suspiciousfeature'), 11 | ] 12 | 13 | operations = [ 14 | migrations.AddField( 15 | model_name='changeset', 16 | name='score', 17 | field=models.IntegerField(null=True, blank=True), 18 | ), 19 | migrations.AddField( 20 | model_name='userdetail', 21 | name='score', 22 | field=models.IntegerField(null=True, blank=True), 23 | ), 24 | ] 25 | -------------------------------------------------------------------------------- /osmchadjango/changeset/migrations/0027_auto_20160321_0605.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import migrations, models 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('changeset', '0026_auto_20160314_1227'), 11 | ] 12 | 13 | operations = [ 14 | migrations.CreateModel( 15 | name='SuspicionScore', 16 | fields=[ 17 | ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), 18 | ('score', models.IntegerField()), 19 | ('reason', models.CharField(max_length=255)), 20 | ('changeset', models.ForeignKey(to='changeset.Changeset', on_delete=models.deletion.CASCADE)), 21 | ], 22 | ), 23 | migrations.CreateModel( 24 | name='UserSuspicionScore', 25 | fields=[ 26 | ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), 27 | ('score', models.IntegerField()), 28 | ('reason', models.CharField(max_length=255)), 29 | ('user', models.ForeignKey(to='changeset.UserDetail', on_delete=models.deletion.CASCADE)), 30 | ], 31 | ), 32 | migrations.AlterUniqueTogether( 33 | name='usersuspicionscore', 34 | unique_together=set([('user', 'score', 'reason')]), 35 | ), 36 | migrations.AlterUniqueTogether( 37 | name='suspicionscore', 38 | unique_together=set([('changeset', 'score', 'reason')]), 39 | ), 40 | ] 41 | -------------------------------------------------------------------------------- /osmchadjango/changeset/migrations/0028_auto_20160321_1035.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import migrations, models 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('changeset', '0027_auto_20160321_0605'), 11 | ] 12 | 13 | operations = [ 14 | migrations.AddField( 15 | model_name='userdetail', 16 | name='contributor_img', 17 | field=models.CharField(max_length=512, null=True, blank=True), 18 | ), 19 | migrations.AddField( 20 | model_name='userdetail', 21 | name='nodes_rank', 22 | field=models.IntegerField(null=True, blank=True), 23 | ), 24 | migrations.AddField( 25 | model_name='userdetail', 26 | name='notes_closed', 27 | field=models.IntegerField(null=True, blank=True), 28 | ), 29 | migrations.AddField( 30 | model_name='userdetail', 31 | name='notes_commented', 32 | field=models.IntegerField(null=True, blank=True), 33 | ), 34 | migrations.AddField( 35 | model_name='userdetail', 36 | name='notes_opened', 37 | field=models.IntegerField(null=True, blank=True), 38 | ), 39 | migrations.AddField( 40 | model_name='userdetail', 41 | name='relations_rank', 42 | field=models.IntegerField(null=True, blank=True), 43 | ), 44 | migrations.AddField( 45 | model_name='userdetail', 46 | name='ways_rank', 47 | field=models.IntegerField(null=True, blank=True), 48 | ), 49 | ] 50 | -------------------------------------------------------------------------------- /osmchadjango/changeset/migrations/0029_suspiciousfeature_timestamp.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import migrations, models 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('changeset', '0028_auto_20160321_1035'), 11 | ] 12 | 13 | operations = [ 14 | migrations.AddField( 15 | model_name='suspiciousfeature', 16 | name='timestamp', 17 | field=models.DateTimeField(auto_now_add=True, null=True), 18 | ), 19 | ] 20 | -------------------------------------------------------------------------------- /osmchadjango/changeset/migrations/0030_auto_20170201_1617.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.10 on 2017-02-01 16:17 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('changeset', '0029_suspiciousfeature_timestamp'), 12 | ] 13 | 14 | operations = [ 15 | migrations.AlterUniqueTogether( 16 | name='suspicionscore', 17 | unique_together=set([]), 18 | ), 19 | migrations.RemoveField( 20 | model_name='suspicionscore', 21 | name='changeset', 22 | ), 23 | migrations.RemoveField( 24 | model_name='suspiciousfeature', 25 | name='changeset', 26 | ), 27 | migrations.RemoveField( 28 | model_name='suspiciousfeature', 29 | name='reasons', 30 | ), 31 | migrations.AlterUniqueTogether( 32 | name='usersuspicionscore', 33 | unique_together=set([]), 34 | ), 35 | migrations.RemoveField( 36 | model_name='usersuspicionscore', 37 | name='user', 38 | ), 39 | migrations.RemoveField( 40 | model_name='changeset', 41 | name='user_detail', 42 | ), 43 | migrations.DeleteModel( 44 | name='SuspicionScore', 45 | ), 46 | migrations.DeleteModel( 47 | name='SuspiciousFeature', 48 | ), 49 | migrations.DeleteModel( 50 | name='UserDetail', 51 | ), 52 | migrations.DeleteModel( 53 | name='UserSuspicionScore', 54 | ), 55 | ] 56 | -------------------------------------------------------------------------------- /osmchadjango/changeset/migrations/0031_remove_changeset_score.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.10 on 2017-02-01 19:33 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('changeset', '0030_auto_20170201_1617'), 12 | ] 13 | 14 | operations = [ 15 | migrations.RemoveField( 16 | model_name='changeset', 17 | name='score', 18 | ), 19 | ] 20 | -------------------------------------------------------------------------------- /osmchadjango/changeset/migrations/0032_auto_20170210_0013.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.10 on 2017-02-10 00:13 3 | from __future__ import unicode_literals 4 | 5 | import django.contrib.gis.db.models.fields 6 | from django.db import migrations, models 7 | 8 | 9 | class Migration(migrations.Migration): 10 | 11 | dependencies = [ 12 | ('changeset', '0031_remove_changeset_score'), 13 | ] 14 | 15 | operations = [ 16 | migrations.AlterField( 17 | model_name='changeset', 18 | name='bbox', 19 | field=django.contrib.gis.db.models.fields.PolygonField(null=True, srid=4326), 20 | ), 21 | migrations.AlterField( 22 | model_name='changeset', 23 | name='checked', 24 | field=models.BooleanField(db_index=True, default=False), 25 | ), 26 | migrations.AlterField( 27 | model_name='changeset', 28 | name='comment', 29 | field=models.CharField(blank=True, db_index=True, max_length=1000, null=True), 30 | ), 31 | migrations.AlterField( 32 | model_name='changeset', 33 | name='date', 34 | field=models.DateTimeField(db_index=True, null=True), 35 | ), 36 | migrations.AlterField( 37 | model_name='changeset', 38 | name='editor', 39 | field=models.CharField(blank=True, db_index=True, max_length=255, null=True), 40 | ), 41 | migrations.AlterField( 42 | model_name='changeset', 43 | name='harmful', 44 | field=models.NullBooleanField(db_index=True), 45 | ), 46 | migrations.AlterField( 47 | model_name='userwhitelist', 48 | name='whitelist_user', 49 | field=models.CharField(db_index=True, max_length=1000), 50 | ), 51 | ] 52 | -------------------------------------------------------------------------------- /osmchadjango/changeset/migrations/0033_suspicionreasons_description.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.10 on 2017-02-23 21:01 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('changeset', '0032_auto_20170210_0013'), 12 | ] 13 | 14 | operations = [ 15 | migrations.AddField( 16 | model_name='suspicionreasons', 17 | name='description', 18 | field=models.CharField(blank=True, max_length=1000), 19 | ), 20 | ] 21 | -------------------------------------------------------------------------------- /osmchadjango/changeset/migrations/0034_suspicionreasons_is_visible.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.10 on 2017-02-23 21:19 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('changeset', '0033_suspicionreasons_description'), 12 | ] 13 | 14 | operations = [ 15 | migrations.AddField( 16 | model_name='suspicionreasons', 17 | name='is_visible', 18 | field=models.BooleanField(default=True), 19 | ), 20 | ] 21 | -------------------------------------------------------------------------------- /osmchadjango/changeset/migrations/0035_auto_20170224_0116.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.10 on 2017-02-24 01:16 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('changeset', '0034_suspicionreasons_is_visible'), 12 | ] 13 | 14 | operations = [ 15 | migrations.AddField( 16 | model_name='suspicionreasons', 17 | name='available_to_changeset', 18 | field=models.BooleanField(default=True), 19 | ), 20 | migrations.AddField( 21 | model_name='suspicionreasons', 22 | name='available_to_feature', 23 | field=models.BooleanField(default=True), 24 | ), 25 | ] 26 | -------------------------------------------------------------------------------- /osmchadjango/changeset/migrations/0036_harmfulreason.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.10 on 2017-02-24 01:48 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('changeset', '0035_auto_20170224_0116'), 12 | ] 13 | 14 | operations = [ 15 | migrations.CreateModel( 16 | name='HarmfulReason', 17 | fields=[ 18 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 19 | ('name', models.CharField(db_index=True, max_length=255, unique=True)), 20 | ('description', models.CharField(blank=True, max_length=1000)), 21 | ('is_visible', models.BooleanField(default=True)), 22 | ('available_to_changeset', models.BooleanField(default=True)), 23 | ('available_to_feature', models.BooleanField(default=True)), 24 | ], 25 | ), 26 | ] 27 | -------------------------------------------------------------------------------- /osmchadjango/changeset/migrations/0037_auto_20170224_1845.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.10 on 2017-02-24 18:45 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('changeset', '0036_harmfulreason'), 12 | ] 13 | 14 | operations = [ 15 | migrations.RenameField( 16 | model_name='harmfulreason', 17 | old_name='available_to_changeset', 18 | new_name='for_changeset', 19 | ), 20 | migrations.RenameField( 21 | model_name='harmfulreason', 22 | old_name='available_to_feature', 23 | new_name='for_feature', 24 | ), 25 | migrations.RenameField( 26 | model_name='suspicionreasons', 27 | old_name='available_to_changeset', 28 | new_name='for_changeset', 29 | ), 30 | migrations.RenameField( 31 | model_name='suspicionreasons', 32 | old_name='available_to_feature', 33 | new_name='for_feature', 34 | ), 35 | ] 36 | -------------------------------------------------------------------------------- /osmchadjango/changeset/migrations/0038_changeset_harmful_reasons.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.10 on 2017-02-24 20:34 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('changeset', '0037_auto_20170224_1845'), 12 | ] 13 | 14 | operations = [ 15 | migrations.AddField( 16 | model_name='changeset', 17 | name='harmful_reasons', 18 | field=models.ManyToManyField(related_name='changesets', to='changeset.HarmfulReason'), 19 | ), 20 | ] 21 | -------------------------------------------------------------------------------- /osmchadjango/changeset/migrations/0039_auto_20170301_0236.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.10 on 2017-03-01 02:36 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('changeset', '0038_changeset_harmful_reasons'), 12 | ] 13 | 14 | operations = [ 15 | migrations.AlterModelOptions( 16 | name='suspicionreasons', 17 | options={'verbose_name': 'Suspicion reason', 'verbose_name_plural': 'Suspicion reasons'}, 18 | ), 19 | ] 20 | -------------------------------------------------------------------------------- /osmchadjango/changeset/migrations/0040_auto_20170311_0228.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.10 on 2017-03-11 02:28 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('changeset', '0039_auto_20170301_0236'), 12 | ] 13 | 14 | operations = [ 15 | migrations.AlterModelOptions( 16 | name='changeset', 17 | options={'ordering': ['-id']}, 18 | ), 19 | ] 20 | -------------------------------------------------------------------------------- /osmchadjango/changeset/migrations/0040_auto_20170328_1350.py: -------------------------------------------------------------------------------- 1 | 2 | # -*- coding: utf-8 -*- 3 | # Generated by Django 1.10 on 2017-03-30 11:45 4 | from __future__ import unicode_literals 5 | 6 | from django.db import migrations 7 | 8 | 9 | def merge_suspicionreasons(apps, schema_editor): 10 | SuspicionReasons = apps.get_model('changeset', 'SuspicionReasons') 11 | distinct_reasons = SuspicionReasons.objects.all().distinct('name') 12 | for reason in distinct_reasons: 13 | same_reasons = SuspicionReasons.objects.filter(name=reason.name).exclude(pk=reason.pk) 14 | if same_reasons.count() > 0: 15 | for same_reason in same_reasons: 16 | changesets = same_reason.changesets.all() 17 | for c in changesets: 18 | c.reasons.remove(same_reason) 19 | c.reasons.add(reason) 20 | print("deleting %s" % same_reason.name) 21 | same_reason.delete() 22 | 23 | 24 | class Migration(migrations.Migration): 25 | 26 | dependencies = [ 27 | ('changeset', '0039_auto_20170301_0236'), 28 | ] 29 | 30 | operations = [ 31 | migrations.RunPython(merge_suspicionreasons, reverse_code=migrations.RunPython.noop), 32 | ] 33 | -------------------------------------------------------------------------------- /osmchadjango/changeset/migrations/0041_auto_20170317_0238.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.10 on 2017-03-17 02:38 3 | from __future__ import unicode_literals 4 | 5 | from django.conf import settings 6 | from django.db import migrations, models 7 | import django.db.models.deletion 8 | 9 | 10 | class Migration(migrations.Migration): 11 | 12 | dependencies = [ 13 | ('changeset', '0040_auto_20170311_0228'), 14 | ] 15 | 16 | operations = [ 17 | migrations.AlterField( 18 | model_name='userwhitelist', 19 | name='user', 20 | field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='whitelists', to=settings.AUTH_USER_MODEL), 21 | ), 22 | ] 23 | -------------------------------------------------------------------------------- /osmchadjango/changeset/migrations/0041_auto_20170330_1401.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.10 on 2017-03-30 14:01 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('changeset', '0040_auto_20170328_1350'), 12 | ] 13 | 14 | operations = [ 15 | migrations.AlterField( 16 | model_name='suspicionreasons', 17 | name='name', 18 | field=models.CharField(max_length=255), 19 | ), 20 | ] 21 | -------------------------------------------------------------------------------- /osmchadjango/changeset/migrations/0042_auto_20170330_1402.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.10 on 2017-03-30 14:02 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('changeset', '0041_auto_20170330_1401'), 12 | ] 13 | 14 | operations = [ 15 | migrations.AlterField( 16 | model_name='suspicionreasons', 17 | name='name', 18 | field=models.CharField(max_length=255, unique=True), 19 | ), 20 | ] 21 | -------------------------------------------------------------------------------- /osmchadjango/changeset/migrations/0043_auto_20170330_1402.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.10 on 2017-03-30 14:02 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('changeset', '0042_auto_20170330_1402'), 12 | ] 13 | 14 | operations = [ 15 | migrations.AlterField( 16 | model_name='suspicionreasons', 17 | name='name', 18 | field=models.CharField(db_index=True, max_length=255, unique=True), 19 | ), 20 | ] 21 | -------------------------------------------------------------------------------- /osmchadjango/changeset/migrations/0044_merge_20170330_1409.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.10 on 2017-03-30 14:09 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('changeset', '0043_auto_20170330_1402'), 12 | ('changeset', '0041_auto_20170317_0238'), 13 | ] 14 | 15 | operations = [ 16 | ] 17 | -------------------------------------------------------------------------------- /osmchadjango/changeset/migrations/0045_auto_20170410_1218.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.10 on 2017-04-10 12:18 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('changeset', '0044_merge_20170330_1409'), 12 | ] 13 | 14 | operations = [ 15 | migrations.RenameModel( 16 | old_name='HarmfulReason', 17 | new_name='Tag', 18 | ), 19 | migrations.RenameField( 20 | model_name='changeset', 21 | old_name='harmful_reasons', 22 | new_name='tags', 23 | ), 24 | ] 25 | -------------------------------------------------------------------------------- /osmchadjango/changeset/migrations/0046_auto_20170414_2043.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.10 on 2017-04-14 20:43 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('changeset', '0045_auto_20170410_1218'), 12 | ] 13 | 14 | operations = [ 15 | migrations.AlterField( 16 | model_name='changeset', 17 | name='uid', 18 | field=models.CharField(db_index=True, max_length=255, verbose_name='User ID'), 19 | ), 20 | ] 21 | -------------------------------------------------------------------------------- /osmchadjango/changeset/migrations/0047_auto_20170417_1718.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.10 on 2017-04-17 17:18 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('changeset', '0046_auto_20170414_2043'), 12 | ] 13 | 14 | operations = [ 15 | migrations.AlterField( 16 | model_name='changeset', 17 | name='imagery_used', 18 | field=models.CharField(blank=True, db_index=True, max_length=1000, null=True), 19 | ), 20 | migrations.AlterField( 21 | model_name='changeset', 22 | name='source', 23 | field=models.CharField(blank=True, db_index=True, max_length=1000, null=True), 24 | ), 25 | migrations.AlterField( 26 | model_name='suspicionreasons', 27 | name='is_visible', 28 | field=models.BooleanField(db_index=True, default=True), 29 | ), 30 | migrations.AlterField( 31 | model_name='tag', 32 | name='is_visible', 33 | field=models.BooleanField(db_index=True, default=True), 34 | ), 35 | ] 36 | -------------------------------------------------------------------------------- /osmchadjango/changeset/migrations/0048_changeset_area.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.10 on 2017-04-18 21:06 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('changeset', '0047_auto_20170417_1718'), 12 | ] 13 | 14 | operations = [ 15 | migrations.AddField( 16 | model_name='changeset', 17 | name='area', 18 | field=models.FloatField(blank=True, null=True), 19 | ), 20 | ] 21 | -------------------------------------------------------------------------------- /osmchadjango/changeset/migrations/0049_auto_20180307_1417.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.0.3 on 2018-03-07 14:17 2 | 3 | from django.db import migrations 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('changeset', '0048_changeset_area'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterModelOptions( 14 | name='suspicionreasons', 15 | options={'ordering': ['id'], 'verbose_name': 'Suspicion reason', 'verbose_name_plural': 'Suspicion reasons'}, 16 | ), 17 | migrations.AlterModelOptions( 18 | name='tag', 19 | options={'ordering': ['id']}, 20 | ), 21 | migrations.AlterModelOptions( 22 | name='userwhitelist', 23 | options={'ordering': ['-whitelist_user']}, 24 | ), 25 | ] 26 | -------------------------------------------------------------------------------- /osmchadjango/changeset/migrations/0050_auto_20181008_1001.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.0.6 on 2018-10-08 10:01 2 | 3 | import django.contrib.postgres.fields.jsonb 4 | from django.db import migrations, models 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('changeset', '0049_auto_20180307_1417'), 11 | ] 12 | 13 | operations = [ 14 | migrations.AddField( 15 | model_name='changeset', 16 | name='new_features', 17 | field=django.contrib.postgres.fields.jsonb.JSONField(default=list), 18 | ), 19 | migrations.AlterField( 20 | model_name='changeset', 21 | name='uid', 22 | field=models.CharField(blank=True, db_index=True, max_length=255, null=True, verbose_name='User ID'), 23 | ), 24 | ] 25 | -------------------------------------------------------------------------------- /osmchadjango/changeset/migrations/0051_changeset_metadata.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2.13 on 2020-08-04 09:59 2 | 3 | import django.contrib.postgres.fields.jsonb 4 | from django.db import migrations 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('changeset', '0050_auto_20181008_1001'), 11 | ] 12 | 13 | operations = [ 14 | migrations.AddField( 15 | model_name='changeset', 16 | name='metadata', 17 | field=django.contrib.postgres.fields.jsonb.JSONField(default=dict), 18 | ), 19 | ] 20 | -------------------------------------------------------------------------------- /osmchadjango/changeset/migrations/0052_changeset_comments_count.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2.13 on 2020-09-24 13:22 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('changeset', '0051_changeset_metadata'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name='changeset', 15 | name='comments_count', 16 | field=models.IntegerField(db_index=True, null=True), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /osmchadjango/changeset/migrations/0053_auto_20201105_1229.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2.13 on 2020-11-05 12:29 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('changeset', '0052_changeset_comments_count'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterField( 14 | model_name='changeset', 15 | name='comments_count', 16 | field=models.IntegerField(db_index=True, default=0, null=True), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /osmchadjango/changeset/migrations/0054_changeset_reviewed_features.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2.20 on 2021-05-06 11:38 2 | 3 | import django.contrib.postgres.fields.jsonb 4 | from django.db import migrations 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('changeset', '0053_auto_20201105_1229'), 11 | ] 12 | 13 | operations = [ 14 | migrations.AddField( 15 | model_name='changeset', 16 | name='reviewed_features', 17 | field=django.contrib.postgres.fields.jsonb.JSONField(default=list), 18 | ), 19 | ] 20 | -------------------------------------------------------------------------------- /osmchadjango/changeset/migrations/0055_changeset_tag_changes.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2.25 on 2022-02-24 15:37 2 | 3 | import django.contrib.postgres.fields.jsonb 4 | from django.db import migrations 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('changeset', '0054_changeset_reviewed_features'), 11 | ] 12 | 13 | operations = [ 14 | migrations.AddField( 15 | model_name='changeset', 16 | name='tag_changes', 17 | field=django.contrib.postgres.fields.jsonb.JSONField(default=dict), 18 | ), 19 | ] 20 | -------------------------------------------------------------------------------- /osmchadjango/changeset/migrations/0056_auto_20220429_1305.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2.27 on 2022-04-29 13:05 2 | 3 | import django.contrib.postgres.indexes 4 | from django.db import migrations 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('changeset', '0055_changeset_tag_changes'), 11 | ] 12 | 13 | operations = [ 14 | migrations.AddIndex( 15 | model_name='changeset', 16 | index=django.contrib.postgres.indexes.GinIndex(fields=['tag_changes'], name='changeset_c_tag_cha_0e8c71_gin'), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /osmchadjango/changeset/migrations/0057_auto_20220920_1423.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2.28 on 2022-09-20 14:23 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('changeset', '0056_auto_20220429_1305'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterField( 14 | model_name='changeset', 15 | name='checked', 16 | field=models.BooleanField(default=False), 17 | ), 18 | migrations.AlterField( 19 | model_name='changeset', 20 | name='comment', 21 | field=models.CharField(blank=True, max_length=1000, null=True), 22 | ), 23 | migrations.AlterField( 24 | model_name='changeset', 25 | name='comments_count', 26 | field=models.IntegerField(default=0, null=True), 27 | ), 28 | migrations.AlterField( 29 | model_name='changeset', 30 | name='create', 31 | field=models.IntegerField(null=True), 32 | ), 33 | migrations.AlterField( 34 | model_name='changeset', 35 | name='delete', 36 | field=models.IntegerField(null=True), 37 | ), 38 | migrations.AlterField( 39 | model_name='changeset', 40 | name='editor', 41 | field=models.CharField(blank=True, max_length=255, null=True), 42 | ), 43 | migrations.AlterField( 44 | model_name='changeset', 45 | name='harmful', 46 | field=models.NullBooleanField(), 47 | ), 48 | migrations.AlterField( 49 | model_name='changeset', 50 | name='imagery_used', 51 | field=models.CharField(blank=True, max_length=1000, null=True), 52 | ), 53 | migrations.AlterField( 54 | model_name='changeset', 55 | name='is_suspect', 56 | field=models.BooleanField(), 57 | ), 58 | migrations.AlterField( 59 | model_name='changeset', 60 | name='modify', 61 | field=models.IntegerField(null=True), 62 | ), 63 | migrations.AlterField( 64 | model_name='changeset', 65 | name='source', 66 | field=models.CharField(blank=True, max_length=1000, null=True), 67 | ), 68 | migrations.AlterField( 69 | model_name='changeset', 70 | name='uid', 71 | field=models.CharField(blank=True, max_length=255, null=True, verbose_name='User ID'), 72 | ), 73 | ] 74 | -------------------------------------------------------------------------------- /osmchadjango/changeset/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OSMCha/osmcha-django/8dfd85bedfc5389113f7e789f3ad9eab2609ca02/osmchadjango/changeset/migrations/__init__.py -------------------------------------------------------------------------------- /osmchadjango/changeset/templates/changeset/bbox_map.html: -------------------------------------------------------------------------------- 1 | 18 | 19 | -------------------------------------------------------------------------------- /osmchadjango/changeset/templates/changeset/not_allowed.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | {% load i18n %} 3 | 4 | {% block title %}OSMCHA - Operation Not Allowed{% endblock %} 5 | 6 | {% block content %} 7 |

{% trans 'Not Allowed' %}

8 |

{% trans 'You are the author of this changeset, so you can not verify this changeset' %}

9 | {% endblock content %} 10 | -------------------------------------------------------------------------------- /osmchadjango/changeset/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OSMCha/osmcha-django/8dfd85bedfc5389113f7e789f3ad9eab2609ca02/osmchadjango/changeset/tests/__init__.py -------------------------------------------------------------------------------- /osmchadjango/changeset/tests/modelfactories.py: -------------------------------------------------------------------------------- 1 | import json 2 | 3 | from django.contrib.gis.geos import Polygon 4 | from django.contrib.auth import get_user_model 5 | from django.utils import timezone 6 | from django.contrib.gis.geos import Point 7 | 8 | import factory 9 | 10 | from ..models import Changeset, SuspicionReasons, UserWhitelist, Tag 11 | from ...users.models import MappingTeam 12 | 13 | 14 | class UserFactory(factory.django.DjangoModelFactory): 15 | class Meta: 16 | model = get_user_model() 17 | 18 | username = factory.Sequence(lambda n: 'user%d' % n) 19 | name = factory.Sequence(lambda n: 'user %d' % n) 20 | email = factory.LazyAttribute(lambda obj: '%s@example.com' % obj.username) 21 | 22 | 23 | class ChangesetFactory(factory.django.DjangoModelFactory): 24 | class Meta: 25 | model = Changeset 26 | # create is a reserved word of DjangoModelFactory, so we need to rename 27 | # to be able to call it 28 | rename = {'form_create': 'create'} 29 | 30 | form_create = 20 31 | modify = 10 32 | delete = 3 33 | uid = '123123' 34 | user = 'test' 35 | editor = 'Potlatch 2' 36 | imagery_used = 'Mapbox' 37 | comments_count = 1 38 | powerfull_editor = False 39 | date = factory.LazyFunction(timezone.now) 40 | is_suspect = False 41 | bbox = Polygon([ 42 | (-71.0646843, 44.2371354), (-71.0048652, 44.2371354), 43 | (-71.0048652, 44.2430624), (-71.0646843, 44.2430624), 44 | (-71.0646843, 44.2371354) 45 | ]) 46 | metadata = {'changesets_count': 99, 'host': 'https://ideditor.netlify.app'} 47 | 48 | 49 | class SuspectChangesetFactory(ChangesetFactory): 50 | form_create = 2000 51 | modify = 10 52 | delete = 30 53 | is_suspect = True 54 | tag_changes = { 55 | "shop": ["supermarket", "convenience_store", "bicycle", "car"], 56 | "lanes": ["2", "4", "1"] 57 | } 58 | 59 | 60 | class HarmfulChangesetFactory(SuspectChangesetFactory): 61 | checked = True 62 | check_user = factory.SubFactory(UserFactory) 63 | check_date = factory.LazyFunction(timezone.now) 64 | harmful = True 65 | 66 | 67 | class GoodChangesetFactory(HarmfulChangesetFactory): 68 | harmful = False 69 | checked = True 70 | check_user = factory.SubFactory(UserFactory) 71 | check_date = factory.LazyFunction(timezone.now) 72 | 73 | 74 | class SuspicionReasonsFactory(factory.django.DjangoModelFactory): 75 | class Meta: 76 | model = SuspicionReasons 77 | 78 | 79 | class TagFactory(factory.django.DjangoModelFactory): 80 | class Meta: 81 | model = Tag 82 | 83 | 84 | class UserWhitelistFactory(factory.django.DjangoModelFactory): 85 | class Meta: 86 | model = UserWhitelist 87 | 88 | 89 | class MappingTeamFactory(factory.django.DjangoModelFactory): 90 | class Meta: 91 | model = MappingTeam 92 | 93 | name = "Map Company" 94 | trusted = True 95 | created_by = factory.SubFactory(UserFactory) 96 | users = [] 97 | -------------------------------------------------------------------------------- /osmchadjango/changeset/tests/test_fixtures/011.osm.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OSMCha/osmcha-django/8dfd85bedfc5389113f7e789f3ad9eab2609ca02/osmchadjango/changeset/tests/test_fixtures/011.osm.gz -------------------------------------------------------------------------------- /osmchadjango/changeset/tests/test_fixtures/way-23-with-unvisible-reason.json: -------------------------------------------------------------------------------- 1 | { 2 | "geometry": { 3 | "type": "LineString", 4 | "coordinates": [ 5 | [ 41.5082139, 52.5817404 ], [ 41.5083964, 52.5815737 ], [ 41.5090171, 52.5811133 ], 6 | [ 41.5098284, 52.5804884 ], [ 41.5098657, 52.5804596 ], [ 41.5099465, 52.5803974 ], 7 | [ 41.5102895, 52.580127 ], [ 41.511222, 52.5794008 ], [ 41.5122121, 52.5786679 ], 8 | [ 41.5124606, 52.5784839 ], [ 41.5130296, 52.5780596 ], [ 41.513464, 52.5777497 ], 9 | [ 41.5135522, 52.5776818 ], [ 41.5138896, 52.5774223 ], [ 41.5147903, 52.5767461 ] 10 | ] 11 | }, 12 | "type": "Feature", 13 | "id": "way!169218447!23", 14 | "properties": { 15 | "name": "\\u0443\\u043b\\u0438\\u0446\\u0430 \\u041f\\u043e\\u0441\\u043a\\u043e\\u043d\\u043a\\u0438\\u043d\\u0430", 16 | "osm:id": 169218447, 17 | "osm:type": "way", 18 | "osm:version": 23, 19 | "osm:changeset": 42893048, 20 | "osm:timestamp": 1476443255000, 21 | "osm:uid": 4173553, 22 | "osm:user": "Victor Denisow", 23 | "highway": "tertiary", 24 | "surface": "asphalt", 25 | "maxspeed": "40", 26 | "result:name_modified": {}, 27 | "result:new_mapper": { 28 | "cfVersion": 2, 29 | "newMapper": 1 30 | }, 31 | "suspicions": [ 32 | {"reason": "special suspicion reason", "is_visible": false} 33 | ], 34 | "oldVersion": { 35 | "geometry": { 36 | "type": "LineString", 37 | "coordinates": [ 38 | [ 41.5082139, 52.5817404 ], [ 41.5083964, 52.5815737 ], [ 41.5090171, 52.5811133 ], 39 | [ 41.5098284, 52.5804884 ], [ 41.5098657, 52.5804596 ], [ 41.5099465, 52.5803974 ], 40 | [ 41.5102895, 52.580127 ], [ 41.511222, 52.5794008 ], [ 41.5124606, 52.5784839 ], 41 | [ 41.5130296, 52.5780596 ], [ 41.513464, 52.5777497 ], [ 41.5138896, 52.5774223 ], 42 | [ 41.5147903, 52.5767461 ] 43 | ] 44 | }, 45 | "type": "Feature", 46 | "id": "way!169218447!23", 47 | "properties": { 48 | "maxspeed": "60", 49 | "lanes": "2", 50 | "name": "\\u0443\\u043b\\u0438\\u0446\\u0430 \\u041f\\u043e\\u0441\\u043a\\u043e\\u043d\\u043a\\u0438\\u043d\\u0430", 51 | "osm:changeset": 36640459, 52 | "osm:version": 23, 53 | "osm:type": "way", 54 | "osm:uid": 1781497, 55 | "osm:id": 169218447, 56 | "osm:user": "lcat", 57 | "osm:timestamp": 1453065048000, 58 | "highway": "tertiary" 59 | } 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /osmchadjango/changeset/tests/test_fixtures/way-23.json: -------------------------------------------------------------------------------- 1 | { 2 | "geometry": { 3 | "type": "LineString", 4 | "coordinates": [ 5 | [ 41.5082139, 52.5817404 ], [ 41.5083964, 52.5815737 ], [ 41.5090171, 52.5811133 ], 6 | [ 41.5098284, 52.5804884 ], [ 41.5098657, 52.5804596 ], [ 41.5099465, 52.5803974 ], 7 | [ 41.5102895, 52.580127 ], [ 41.511222, 52.5794008 ], [ 41.5122121, 52.5786679 ], 8 | [ 41.5124606, 52.5784839 ], [ 41.5130296, 52.5780596 ], [ 41.513464, 52.5777497 ], 9 | [ 41.5135522, 52.5776818 ], [ 41.5138896, 52.5774223 ], [ 41.5147903, 52.5767461 ] 10 | ] 11 | }, 12 | "type": "Feature", 13 | "id": "way!169218447!23", 14 | "properties": { 15 | "name": "\\u0443\\u043b\\u0438\\u0446\\u0430 \\u041f\\u043e\\u0441\\u043a\\u043e\\u043d\\u043a\\u0438\\u043d\\u0430", 16 | "osm:id": 169218447, 17 | "osm:type": "way", 18 | "osm:version": 23, 19 | "osm:changeset": 42893048, 20 | "osm:timestamp": 1476443255000, 21 | "osm:uid": 4173553, 22 | "osm:user": "Victor Denisow", 23 | "osmcha:note": "Vandalism", 24 | "highway": "tertiary", 25 | "name": "High Street", 26 | "surface": "asphalt", 27 | "maxspeed": "40", 28 | "result:name_modified": {}, 29 | "result:new_mapper": { 30 | "cfVersion": 2, 31 | "newMapper": 1 32 | }, 33 | "suspicions": [ 34 | {"reason": "new mapper edits", "is_visible": true}, 35 | {"reason": "moved an object a significant amount"} 36 | ], 37 | "oldVersion": { 38 | "geometry": { 39 | "type": "LineString", 40 | "coordinates": [ 41 | [ 41.5082139, 52.5817404 ], [ 41.5083964, 52.5815737 ], [ 41.5090171, 52.5811133 ], 42 | [ 41.5098284, 52.5804884 ], [ 41.5098657, 52.5804596 ], [ 41.5099465, 52.5803974 ], 43 | [ 41.5102895, 52.580127 ], [ 41.511222, 52.5794008 ], [ 41.5124606, 52.5784839 ], 44 | [ 41.5130296, 52.5780596 ], [ 41.513464, 52.5777497 ], [ 41.5138896, 52.5774223 ], 45 | [ 41.5147903, 52.5767461 ] 46 | ] 47 | }, 48 | "type": "Feature", 49 | "id": "way!169218447!23", 50 | "properties": { 51 | "maxspeed": "60", 52 | "lanes": "2", 53 | "name": "\\u0443\\u043b\\u0438\\u0446\\u0430 \\u041f\\u043e\\u0441\\u043a\\u043e\\u043d\\u043a\\u0438\\u043d\\u0430", 54 | "osm:changeset": 36640459, 55 | "osm:version": 23, 56 | "osm:type": "way", 57 | "osm:uid": 1781497, 58 | "osm:id": 169218447, 59 | "osm:user": "lcat", 60 | "osm:timestamp": 1453065048000, 61 | "highway": "tertiary" 62 | } 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /osmchadjango/changeset/tests/test_fixtures/way-24.json: -------------------------------------------------------------------------------- 1 | { 2 | "geometry": { 3 | "type": "LineString", 4 | "coordinates": [ 5 | [41.5082139, 52.5817404], [41.5083964, 52.5815737], [41.5090171, 52.5811133], 6 | [41.5098284, 52.5804884], [41.5098657, 52.5804596], [41.5099465, 52.5803974], 7 | [41.5102895, 52.580127], [41.511222, 52.5794008], [41.5122121, 52.5786679], 8 | [41.5124606, 52.5784839], [41.5130296, 52.5780596], [41.513464, 52.5777497], 9 | [41.5135522, 52.5776818], [41.5138896, 52.5774223], [41.5147903, 52.5767461] 10 | ] 11 | }, 12 | "type": "Feature", 13 | "id": "way!169218447!24", 14 | "properties": { 15 | "maxspeed": "60", 16 | "result:new_mapper": { 17 | "cfVersion": 2, 18 | "newMapper": 1 19 | }, 20 | "name": "\\u0443\\u043b\\u0438\\u0446\\u0430 \\u041f\\u043e\\u0441\\u043a\\u043e\\u043d\\u043a\\u0438\\u043d\\u0430", 21 | "osm:id": 169218447, 22 | "oldVersion": { 23 | "geometry": { 24 | "type": "LineString", 25 | "coordinates": [ 26 | [41.5082139, 52.5817404], [41.5083964, 52.5815737], [41.5090171, 52.5811133], 27 | [41.5098284, 52.5804884], [41.5098657, 52.5804596], [41.5099465, 52.5803974], 28 | [41.5102895, 52.580127], [41.511222, 52.5794008], [41.5124606, 52.5784839], 29 | [41.5130296, 52.5780596], [41.513464, 52.5777497], [41.5138896, 52.5774223], 30 | [41.5147903, 52.5767461] 31 | ] 32 | }, 33 | "type": "Feature", 34 | "id": "way!169218447!23", 35 | "properties": { 36 | "maxspeed": "60", 37 | "lanes": "2", 38 | "name": "\\u0443\\u043b\\u0438\\u0446\\u0430 \\u041f\\u043e\\u0441\\u043a\\u043e\\u043d\\u043a\\u0438\\u043d\\u0430", 39 | "osm:changeset": 36640459, 40 | "osm:version": 23, 41 | "osm:type": "way", 42 | "osm:uid": 1781497, 43 | "osm:id": 169218447, 44 | "osm:user": "lcat", 45 | "osm:timestamp": 1453065048000, 46 | "highway": "tertiary" 47 | } 48 | }, 49 | "osm:changeset": 42893148, 50 | "suspicions": [ 51 | { 52 | "reason": "new mapper edits" 53 | } 54 | ], 55 | "osm:type": "way", 56 | "osm:uid": 4173553, 57 | "osm:version": 24, 58 | "osm:user": "Victor Denisow", 59 | "result:name_modified": {}, 60 | "lanes": "2", 61 | "highway": "tertiary", 62 | "osm:timestamp": 1476443256000 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /osmchadjango/changeset/tests/test_review_features_views.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.urls import reverse 5 | 6 | from social_django.models import UserSocialAuth 7 | from rest_framework.test import APITestCase 8 | 9 | from ...users.models import User 10 | from .modelfactories import ChangesetFactory 11 | 12 | 13 | class TestReviewFeaturesAPIView(APITestCase): 14 | def setUp(self): 15 | self.user = User.objects.create_user( 16 | username='test_2', 17 | password='password', 18 | email='a@a.com' 19 | ) 20 | UserSocialAuth.objects.create( 21 | user=self.user, 22 | provider="openstreetmap-oauth2", 23 | uid="345", 24 | extra_data={"id": "345", "access_token": "bbbb"}, 25 | ) 26 | self.changeset = ChangesetFactory(id=31982802) 27 | 28 | def test_review_feature_unauthenticated(self): 29 | response = self.client.put( 30 | reverse( 31 | 'changeset:review-harmful-feature', 32 | args=[self.changeset.id, "node", 1234] 33 | ) 34 | ) 35 | self.assertEqual(response.status_code, 401) 36 | 37 | def test_remove_review_feature_unauthenticated(self): 38 | response = self.client.delete( 39 | reverse( 40 | 'changeset:review-harmful-feature', 41 | args=[self.changeset.id, "node", 1234] 42 | ) 43 | ) 44 | self.assertEqual(response.status_code, 401) 45 | 46 | def test_remove_review_feature(self): 47 | self.client.login(username=self.user.username, password='password') 48 | response = self.client.delete( 49 | reverse( 50 | 'changeset:review-harmful-feature', 51 | args=[self.changeset.id, "node", 1234] 52 | ) 53 | ) 54 | self.assertEqual(response.status_code, 400) 55 | self.changeset.refresh_from_db() 56 | self.assertEqual(self.changeset.reviewed_features, []) 57 | # now add a feature to the changeset 58 | response = self.client.put( 59 | reverse( 60 | 'changeset:review-harmful-feature', 61 | args=[self.changeset.id, "node", 1234] 62 | ) 63 | ) 64 | self.assertEqual(response.status_code, 200) 65 | self.changeset.refresh_from_db() 66 | self.assertEqual(self.changeset.reviewed_features, [{"id": "node-1234", "user": "test_2"}]) 67 | # and remove the feature should work 68 | response = self.client.delete( 69 | reverse( 70 | 'changeset:review-harmful-feature', 71 | args=[self.changeset.id, "node", 1234] 72 | ) 73 | ) 74 | self.assertEqual(response.status_code, 200) 75 | self.changeset.refresh_from_db() 76 | self.assertEqual(self.changeset.reviewed_features, []) 77 | 78 | def test_review_feature_own_changeset(self): 79 | changeset = ChangesetFactory(id=457890, uid="345", user="test_2") 80 | self.client.login(username=self.user.username, password='password') 81 | response = self.client.put( 82 | reverse( 83 | 'changeset:review-harmful-feature', 84 | args=[changeset.id, "node", 1234] 85 | ) 86 | ) 87 | self.assertEqual(response.status_code, 403) 88 | 89 | def test_remove_review_feature_own_changeset(self): 90 | changeset = ChangesetFactory( 91 | id=457890, 92 | uid="345", 93 | user="test_2", 94 | reviewed_features=[{"id": "node-1234", "user": "test"}] 95 | ) 96 | self.client.login(username=self.user.username, password='password') 97 | response = self.client.delete( 98 | reverse( 99 | 'changeset:review-harmful-feature', 100 | args=[changeset.id, "node", 1234] 101 | ) 102 | ) 103 | self.assertEqual(response.status_code, 403) 104 | -------------------------------------------------------------------------------- /osmchadjango/changeset/throttling.py: -------------------------------------------------------------------------------- 1 | from rest_framework.throttling import UserRateThrottle 2 | 3 | 4 | class NonStaffUserThrottle(UserRateThrottle): 5 | scope = 'non_staff_user' 6 | 7 | def get_cache_key(self, request, view): 8 | if request.user.is_authenticated: 9 | if request.user.is_staff: 10 | return None 11 | else: 12 | ident = request.user.pk 13 | else: 14 | ident = self.get_ident(request) 15 | 16 | return self.cache_format % { 17 | 'scope': self.scope, 18 | 'ident': ident 19 | } 20 | -------------------------------------------------------------------------------- /osmchadjango/contrib/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | -------------------------------------------------------------------------------- /osmchadjango/contrib/sites/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | -------------------------------------------------------------------------------- /osmchadjango/contrib/sites/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import models, migrations 5 | import django.contrib.sites.models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ] 12 | 13 | operations = [ 14 | migrations.CreateModel( 15 | name='Site', 16 | fields=[ 17 | ('id', models.AutoField(verbose_name='ID', primary_key=True, serialize=False, auto_created=True)), 18 | ('domain', models.CharField(verbose_name='domain name', max_length=100, validators=[django.contrib.sites.models._simple_domain_name_validator])), 19 | ('name', models.CharField(verbose_name='display name', max_length=50)), 20 | ], 21 | options={ 22 | 'verbose_name_plural': 'sites', 23 | 'verbose_name': 'site', 24 | 'db_table': 'django_site', 25 | 'ordering': ('domain',), 26 | }, 27 | managers=[ 28 | ('objects', django.contrib.sites.models.SiteManager()), 29 | ], 30 | ), 31 | ] 32 | -------------------------------------------------------------------------------- /osmchadjango/contrib/sites/migrations/0002_set_site_domain_and_name.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.conf import settings 5 | from django.db import migrations 6 | 7 | 8 | def update_site_forward(apps, schema_editor): 9 | """Set site domain and name.""" 10 | Site = apps.get_model("sites", "Site") 11 | Site.objects.update_or_create( 12 | id=settings.SITE_ID, 13 | defaults={ 14 | "domain": "example.com", 15 | "name": "osmcha-django" 16 | } 17 | ) 18 | 19 | 20 | def update_site_backward(apps, schema_editor): 21 | """Revert site domain and name to default.""" 22 | Site = apps.get_model("sites", "Site") 23 | Site.objects.update_or_create( 24 | id=settings.SITE_ID, 25 | defaults={ 26 | "domain": "example.com", 27 | "name": "example.com" 28 | } 29 | ) 30 | 31 | 32 | class Migration(migrations.Migration): 33 | 34 | dependencies = [ 35 | ('sites', '0001_initial'), 36 | ] 37 | 38 | operations = [ 39 | migrations.RunPython(update_site_forward, update_site_backward), 40 | ] 41 | -------------------------------------------------------------------------------- /osmchadjango/contrib/sites/migrations/0003_auto_20151124_0124.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import migrations, models 5 | import django.contrib.sites.models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('sites', '0002_set_site_domain_and_name'), 12 | ] 13 | 14 | operations = [ 15 | migrations.AlterModelManagers( 16 | name='site', 17 | managers=[ 18 | ('objects', django.contrib.sites.models.SiteManager()), 19 | ], 20 | ), 21 | ] 22 | -------------------------------------------------------------------------------- /osmchadjango/contrib/sites/migrations/0003_auto_20161005_1234.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.10 on 2016-10-05 12:34 3 | from __future__ import unicode_literals 4 | 5 | import django.contrib.sites.models 6 | from django.db import migrations, models 7 | 8 | 9 | class Migration(migrations.Migration): 10 | 11 | dependencies = [ 12 | ('sites', '0002_set_site_domain_and_name'), 13 | ] 14 | 15 | operations = [ 16 | migrations.AlterField( 17 | model_name='site', 18 | name='domain', 19 | field=models.CharField(max_length=100, unique=True, validators=[django.contrib.sites.models._simple_domain_name_validator], verbose_name='domain name'), 20 | ), 21 | ] 22 | -------------------------------------------------------------------------------- /osmchadjango/contrib/sites/migrations/0004_merge_20170130_1414.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.10 on 2017-01-30 14:14 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('sites', '0003_auto_20151124_0124'), 12 | ('sites', '0003_auto_20161005_1234'), 13 | ] 14 | 15 | operations = [ 16 | ] 17 | -------------------------------------------------------------------------------- /osmchadjango/contrib/sites/migrations/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | -------------------------------------------------------------------------------- /osmchadjango/roulette_integration/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OSMCha/osmcha-django/8dfd85bedfc5389113f7e789f3ad9eab2609ca02/osmchadjango/roulette_integration/__init__.py -------------------------------------------------------------------------------- /osmchadjango/roulette_integration/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | # Register your models here. 4 | -------------------------------------------------------------------------------- /osmchadjango/roulette_integration/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class RouletteIntegrationConfig(AppConfig): 5 | name = 'roulette_integration' 6 | -------------------------------------------------------------------------------- /osmchadjango/roulette_integration/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2.11 on 2020-06-01 01:08 2 | 3 | from django.conf import settings 4 | from django.db import migrations, models 5 | import django.db.models.deletion 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | initial = True 11 | 12 | dependencies = [ 13 | ('changeset', '0050_auto_20181008_1001'), 14 | migrations.swappable_dependency(settings.AUTH_USER_MODEL), 15 | ] 16 | 17 | operations = [ 18 | migrations.CreateModel( 19 | name='ChallengeIntegration', 20 | fields=[ 21 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 22 | ('challenge_id', models.IntegerField(db_index=True, unique=True)), 23 | ('active', models.BooleanField(db_index=True, default=True)), 24 | ('created', models.DateTimeField(auto_now_add=True)), 25 | ('reasons', models.ManyToManyField(related_name='challenges', to='changeset.SuspicionReasons')), 26 | ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='challenges', to=settings.AUTH_USER_MODEL)), 27 | ], 28 | options={ 29 | 'verbose_name': 'Challenge Integration', 30 | 'verbose_name_plural': 'Challenge Integrations', 31 | 'ordering': ['id'], 32 | }, 33 | ), 34 | ] 35 | -------------------------------------------------------------------------------- /osmchadjango/roulette_integration/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OSMCha/osmcha-django/8dfd85bedfc5389113f7e789f3ad9eab2609ca02/osmchadjango/roulette_integration/migrations/__init__.py -------------------------------------------------------------------------------- /osmchadjango/roulette_integration/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | from django.contrib.auth import get_user_model 3 | 4 | from ..changeset.models import SuspicionReasons 5 | 6 | User = get_user_model() 7 | 8 | 9 | class ChallengeIntegration(models.Model): 10 | challenge_id = models.IntegerField(unique=True, db_index=True) 11 | reasons = models.ManyToManyField(SuspicionReasons, related_name='challenges') 12 | active = models.BooleanField(default=True, db_index=True) 13 | created = models.DateTimeField(auto_now_add=True) 14 | user = models.ForeignKey(User, related_name="challenges", on_delete=models.CASCADE) 15 | 16 | def __str__(self): 17 | return 'Challenge {}'.format(self.challenge_id) 18 | 19 | class Meta: 20 | ordering = ['id'] 21 | verbose_name = 'Challenge Integration' 22 | verbose_name_plural = 'Challenge Integrations' 23 | -------------------------------------------------------------------------------- /osmchadjango/roulette_integration/serializers.py: -------------------------------------------------------------------------------- 1 | from rest_framework.fields import HiddenField, CurrentUserDefault, ReadOnlyField 2 | from rest_framework.serializers import ModelSerializer, PrimaryKeyRelatedField 3 | 4 | from ..changeset.serializers import SuspicionReasonsSerializer 5 | from .models import ChallengeIntegration 6 | 7 | 8 | class ChallengeIntegrationSerializer(ModelSerializer): 9 | user = HiddenField(default=CurrentUserDefault()) 10 | owner = ReadOnlyField(source='user.username', default=None) 11 | 12 | # def create(self, validated_data): 13 | # import ipdb; ipdb.set_trace() 14 | # reasons_data = [i.get('id') for i in validated_data.pop('reasons')] 15 | # obj = ChallengeIntegration.objects.create(**validated_data) 16 | 17 | class Meta: 18 | model = ChallengeIntegration 19 | fields = ['id', 'challenge_id', 'reasons', 'user', 'active', 'created', 'owner'] 20 | read_only_fields = ('created', 'owner') 21 | -------------------------------------------------------------------------------- /osmchadjango/roulette_integration/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OSMCha/osmcha-django/8dfd85bedfc5389113f7e789f3ad9eab2609ca02/osmchadjango/roulette_integration/tests/__init__.py -------------------------------------------------------------------------------- /osmchadjango/roulette_integration/tests/test_models.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime 2 | 3 | from django.core.exceptions import ValidationError 4 | from django.contrib.auth import get_user_model 5 | from django.db.utils import IntegrityError 6 | from django.test import TestCase 7 | 8 | from ..models import ChallengeIntegration 9 | 10 | from ...changeset.tests.modelfactories import SuspicionReasonsFactory, UserFactory 11 | 12 | 13 | class TestMapRouletteChallengeModel(TestCase): 14 | def setUp(self): 15 | self.user = UserFactory(username='test_user') 16 | reason_1 = SuspicionReasonsFactory(name="Grafitti") 17 | self.integration = ChallengeIntegration.objects.create(challenge_id=1234, user=self.user) 18 | self.integration.reasons.add(reason_1) 19 | 20 | def test_challenge_creation(self): 21 | self.assertIsInstance(self.integration, ChallengeIntegration) 22 | self.assertEqual(self.integration.challenge_id, 1234) 23 | self.assertTrue(self.integration.active) 24 | self.assertIsNotNone(self.integration.created) 25 | self.assertIsInstance(self.integration.created, datetime) 26 | self.assertIsNotNone(self.integration.user) 27 | self.assertIsInstance(self.integration.user, get_user_model()) 28 | self.assertEqual(self.integration.__str__(), 'Challenge 1234') 29 | 30 | def test_required_user_field(self): 31 | with self.assertRaises(IntegrityError): 32 | ChallengeIntegration.objects.create(challenge_id=1234) 33 | 34 | def test_required_challenge_id_field(self): 35 | with self.assertRaises(IntegrityError): 36 | ChallengeIntegration.objects.create(user=self.user) 37 | -------------------------------------------------------------------------------- /osmchadjango/roulette_integration/tests/test_utils.py: -------------------------------------------------------------------------------- 1 | import json 2 | import sys 3 | from unittest import mock, skipIf 4 | import requests 5 | 6 | from django.test import TestCase, override_settings 7 | from django.conf import settings 8 | 9 | from ..utils import ( 10 | format_challenge_task_payload, 11 | push_feature_to_maproulette, 12 | remove_unneeded_properties 13 | ) 14 | from ...changeset.tests.modelfactories import SuspicionReasonsFactory 15 | 16 | class TestFormatChallengePayload(TestCase): 17 | def setUp(self): 18 | self.feature = { 19 | 'type': 'Feature', 20 | 'geometry': { 21 | 'type': 'Point', 22 | 'coordinates': [-110.9595328, 32.2263734] 23 | }, 24 | 'properties': { 25 | 'highway': 'traffic_signals', 26 | 'crossing': 'marked', 27 | 'traffic_signals': 'signal', 28 | 'osm:id': 1234 29 | }, 30 | } 31 | self.task_payload = { 32 | "parent": 1234, 33 | "name": "987", 34 | "geometries": {"features": [ 35 | { 36 | 'type': 'Feature', 37 | 'geometry': { 38 | 'type': 'Point', 39 | 'coordinates': [-110.9595328, 32.2263734] 40 | }, 41 | 'properties': { 42 | 'highway': 'traffic_signals', 43 | 'crossing': 'marked', 44 | 'traffic_signals': 'signal', 45 | 'osmcha_reasons': 'New Mapper, Vandalism' 46 | }, 47 | } 48 | ]} 49 | } 50 | 51 | @skipIf( 52 | sys.version_info < (3,6), 53 | "Python 3.5 has a different dict ordering that makes this test to fail" 54 | ) 55 | def test_format_challenge_task_payload(self): 56 | self.assertEqual( 57 | format_challenge_task_payload( 58 | self.feature, 1234, 987, ['New Mapper', 'Vandalism'] 59 | ), 60 | json.dumps(self.task_payload) 61 | ) 62 | 63 | @override_settings(MAP_ROULETTE_API_KEY='xyz') 64 | @override_settings(MAP_ROULETTE_API_URL='https://maproulette.org/api/v2') 65 | @mock.patch.object(requests, 'post') 66 | def test_push_feature_to_maproulette(self, mocked_post): 67 | class MockResponse(): 68 | status_code = 200 69 | mocked_post.return_value = MockResponse 70 | 71 | push_feature_to_maproulette(self.feature, 1234, 987) 72 | mocked_post.assert_called_with( 73 | 'https://maproulette.org/api/v2/task', 74 | headers={ 75 | "Content-Type": "application/json", 76 | "apiKey": "xyz" 77 | }, 78 | data=format_challenge_task_payload(self.feature, 1234, 987) 79 | ) 80 | 81 | 82 | class TestRemoveUnneededProperties(TestCase): 83 | def test_properties_are_removed(self): 84 | self.fixture = json.load(open( 85 | settings.APPS_DIR.path('changeset/tests/test_fixtures/way-23.json')(), 86 | 'r' 87 | )) 88 | fixture_keys = remove_unneeded_properties(self.fixture)['properties'].keys() 89 | self.assertNotIn('result:new_mapper', fixture_keys) 90 | self.assertNotIn('osm:id', fixture_keys) 91 | self.assertNotIn('osm:changeset', fixture_keys) 92 | self.assertNotIn('oldVersion', fixture_keys) 93 | -------------------------------------------------------------------------------- /osmchadjango/roulette_integration/urls.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import absolute_import, unicode_literals 3 | 4 | from django.urls import re_path 5 | 6 | from . import views 7 | 8 | 9 | app_name = 'roulette_integration' 10 | urlpatterns = [ 11 | re_path( 12 | r'^challenges/$', 13 | view=views.ChallengeIntegrationListCreateAPIView.as_view(), 14 | name='list-create' 15 | ), 16 | re_path( 17 | r'^challenges/(?P\d+)/$', 18 | view=views.ChallengeIntegrationDetailAPIView.as_view(), 19 | name='detail' 20 | ), 21 | ] 22 | -------------------------------------------------------------------------------- /osmchadjango/roulette_integration/utils.py: -------------------------------------------------------------------------------- 1 | import json 2 | from os.path import join 3 | import requests 4 | 5 | from django.conf import settings 6 | 7 | 8 | def remove_unneeded_properties(feature): 9 | keys_to_remove = [ 10 | key for key in feature['properties'].keys() 11 | if key.startswith('osm:') or key.startswith('result:') 12 | ] 13 | for key in keys_to_remove: 14 | feature['properties'].pop(key) 15 | 16 | if feature['properties'].get('oldVersion'): 17 | feature['properties'].pop('oldVersion') 18 | if feature['properties'].get('suspicions'): 19 | feature['properties'].pop('suspicions') 20 | 21 | return feature 22 | 23 | 24 | def format_challenge_task_payload(feature, challenge_id, name, reasons=[]): 25 | if len(reasons): 26 | feature['properties']['osmcha_reasons'] = ", ".join([i for i in reasons]) 27 | payload = { 28 | "parent": challenge_id, 29 | "name": "{}".format(name), 30 | "geometries": {"features": [remove_unneeded_properties(feature)]} 31 | } 32 | return json.dumps(payload) 33 | 34 | 35 | def push_feature_to_maproulette(feature, challenge_id, name, reasons=[]): 36 | if (settings.MAP_ROULETTE_API_KEY is not None and 37 | settings.MAP_ROULETTE_API_URL is not None): 38 | payload = format_challenge_task_payload( 39 | feature, challenge_id, name, reasons 40 | ) 41 | headers = { 42 | "Content-Type": "application/json", 43 | "apiKey": settings.MAP_ROULETTE_API_KEY 44 | } 45 | return requests.post( 46 | join(settings.MAP_ROULETTE_API_URL, 'task'), 47 | headers=headers, 48 | data=payload 49 | ) 50 | -------------------------------------------------------------------------------- /osmchadjango/roulette_integration/views.py: -------------------------------------------------------------------------------- 1 | from rest_framework.generics import ListCreateAPIView, RetrieveUpdateDestroyAPIView 2 | from rest_framework.permissions import IsAdminUser 3 | from rest_framework.filters import OrderingFilter 4 | 5 | from .serializers import ChallengeIntegrationSerializer 6 | from .models import ChallengeIntegration 7 | 8 | 9 | class ChallengeIntegrationListCreateAPIView(ListCreateAPIView): 10 | """ 11 | get: 12 | List all MapRoulette challenges. 13 | It can be ordered by 'created' or 'challenge_id'. The default ordering is by '-created'. 14 | 15 | post: 16 | Create a MapRoulette challenge. 17 | It requires a challenge_id and a list of SuspicionReasons. The challenge_id 18 | must be unique. This endpoint is restricted to staff users. 19 | """ 20 | permission_classes = (IsAdminUser,) 21 | serializer_class = ChallengeIntegrationSerializer 22 | filter_backends = (OrderingFilter,) 23 | ordering_fields = ('created', 'challenge_id') 24 | ordering = '-created' 25 | queryset = ChallengeIntegration.objects.all() 26 | 27 | def perform_create(self, serializer): 28 | serializer.save(user=self.request.user) 29 | 30 | 31 | class ChallengeIntegrationDetailAPIView(RetrieveUpdateDestroyAPIView): 32 | """ 33 | get: 34 | Get details of a MapRoulette challenge. 35 | Only staff users can access it. 36 | 37 | put: 38 | Update an MapRoulette challenge. 39 | Only staff users can update it. 40 | 41 | patch: 42 | Update an MapRoulette challenge. 43 | Only staff users can update it. 44 | 45 | delete: 46 | Delete an MapRoulette challenge. 47 | Only staff users can delete it. 48 | """ 49 | permission_classes = (IsAdminUser,) 50 | serializer_class = ChallengeIntegrationSerializer 51 | queryset = ChallengeIntegration.objects.all() 52 | -------------------------------------------------------------------------------- /osmchadjango/supervise/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OSMCha/osmcha-django/8dfd85bedfc5389113f7e789f3ad9eab2609ca02/osmchadjango/supervise/__init__.py -------------------------------------------------------------------------------- /osmchadjango/supervise/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib.gis import admin 2 | 3 | from .models import AreaOfInterest 4 | 5 | 6 | admin.site.register(AreaOfInterest, admin.GeoModelAdmin) 7 | -------------------------------------------------------------------------------- /osmchadjango/supervise/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class SuperviseConfig(AppConfig): 5 | name = 'supervise' 6 | -------------------------------------------------------------------------------- /osmchadjango/supervise/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.10 on 2017-02-25 03:30 3 | from __future__ import unicode_literals 4 | 5 | from django.conf import settings 6 | import django.contrib.gis.db.models.fields 7 | import django.contrib.postgres.fields.jsonb 8 | from django.db import migrations, models 9 | import django.db.models.deletion 10 | 11 | 12 | class Migration(migrations.Migration): 13 | 14 | initial = True 15 | 16 | dependencies = [ 17 | migrations.swappable_dependency(settings.AUTH_USER_MODEL), 18 | ] 19 | 20 | operations = [ 21 | migrations.CreateModel( 22 | name='AreaOfInterest', 23 | fields=[ 24 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 25 | ('name', models.CharField(blank=True, max_length=255)), 26 | ('filters', django.contrib.postgres.fields.jsonb.JSONField(blank=True, null=True)), 27 | ('place', django.contrib.gis.db.models.fields.MultiPolygonField(srid=4326)), 28 | ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), 29 | ], 30 | ), 31 | ] 32 | -------------------------------------------------------------------------------- /osmchadjango/supervise/migrations/0002_auto_20170227_1257.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.10 on 2017-02-27 12:57 3 | from __future__ import unicode_literals 4 | 5 | from django.conf import settings 6 | from django.db import migrations 7 | 8 | 9 | class Migration(migrations.Migration): 10 | 11 | dependencies = [ 12 | migrations.swappable_dependency(settings.AUTH_USER_MODEL), 13 | ('supervise', '0001_initial'), 14 | ] 15 | 16 | operations = [ 17 | migrations.AlterUniqueTogether( 18 | name='areaofinterest', 19 | unique_together=set([('user', 'name')]), 20 | ), 21 | ] 22 | -------------------------------------------------------------------------------- /osmchadjango/supervise/migrations/0003_auto_20170227_1320.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.10 on 2017-02-27 13:20 3 | from __future__ import unicode_literals 4 | 5 | import django.contrib.gis.db.models.fields 6 | from django.db import migrations 7 | 8 | 9 | class Migration(migrations.Migration): 10 | 11 | dependencies = [ 12 | ('supervise', '0002_auto_20170227_1257'), 13 | ] 14 | 15 | operations = [ 16 | migrations.AlterField( 17 | model_name='areaofinterest', 18 | name='place', 19 | field=django.contrib.gis.db.models.fields.MultiPolygonField(blank=True, null=True, srid=4326), 20 | ), 21 | ] 22 | -------------------------------------------------------------------------------- /osmchadjango/supervise/migrations/0004_auto_20170405_2012.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.10 on 2017-04-05 20:12 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('supervise', '0003_auto_20170227_1320'), 12 | ] 13 | 14 | operations = [ 15 | migrations.RenameField( 16 | model_name='areaofinterest', 17 | old_name='place', 18 | new_name='geometry', 19 | ), 20 | ] 21 | -------------------------------------------------------------------------------- /osmchadjango/supervise/migrations/0005_auto_20170405_2327.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.10 on 2017-04-05 23:27 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | import uuid 7 | 8 | 9 | class Migration(migrations.Migration): 10 | 11 | dependencies = [ 12 | ('supervise', '0004_auto_20170405_2012'), 13 | ] 14 | 15 | operations = [ 16 | migrations.RemoveField( 17 | model_name='areaofinterest', 18 | name='id', 19 | ), 20 | migrations.AddField( 21 | model_name='areaofinterest', 22 | name='uuid', 23 | field=models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False), 24 | ), 25 | ] 26 | -------------------------------------------------------------------------------- /osmchadjango/supervise/migrations/0006_auto_20170405_2328.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.10 on 2017-04-05 23:28 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('supervise', '0005_auto_20170405_2327'), 12 | ] 13 | 14 | operations = [ 15 | migrations.RenameField( 16 | model_name='areaofinterest', 17 | old_name='uuid', 18 | new_name='id', 19 | ), 20 | ] 21 | -------------------------------------------------------------------------------- /osmchadjango/supervise/migrations/0007_auto_20170504_2109.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.10.7 on 2017-05-04 21:09 3 | from __future__ import unicode_literals 4 | 5 | import django.contrib.gis.db.models.fields 6 | from django.db import migrations 7 | 8 | 9 | class Migration(migrations.Migration): 10 | 11 | dependencies = [ 12 | ('supervise', '0006_auto_20170405_2328'), 13 | ] 14 | 15 | operations = [ 16 | migrations.AlterModelOptions( 17 | name='areaofinterest', 18 | options={'verbose_name': 'Area of Interest', 'verbose_name_plural': 'Areas of Interest'}, 19 | ), 20 | migrations.AlterField( 21 | model_name='areaofinterest', 22 | name='geometry', 23 | field=django.contrib.gis.db.models.fields.GeometryField(blank=True, null=True, srid=4326), 24 | ), 25 | ] 26 | -------------------------------------------------------------------------------- /osmchadjango/supervise/migrations/0008_auto_20170504_2119.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.10.7 on 2017-05-04 21:19 3 | from __future__ import unicode_literals 4 | 5 | import django.contrib.postgres.fields.jsonb 6 | from django.db import migrations 7 | 8 | 9 | class Migration(migrations.Migration): 10 | 11 | dependencies = [ 12 | ('supervise', '0007_auto_20170504_2109'), 13 | ] 14 | 15 | operations = [ 16 | migrations.AlterField( 17 | model_name='areaofinterest', 18 | name='filters', 19 | field=django.contrib.postgres.fields.jsonb.JSONField(default={'in_bbox': '-180,-180,180,180'}), 20 | preserve_default=False, 21 | ), 22 | ] 23 | -------------------------------------------------------------------------------- /osmchadjango/supervise/migrations/0009_blacklisteduser.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.10.7 on 2017-05-17 23:33 3 | from __future__ import unicode_literals 4 | 5 | from django.conf import settings 6 | from django.db import migrations, models 7 | import django.db.models.deletion 8 | 9 | 10 | class Migration(migrations.Migration): 11 | 12 | dependencies = [ 13 | migrations.swappable_dependency(settings.AUTH_USER_MODEL), 14 | ('supervise', '0008_auto_20170504_2119'), 15 | ] 16 | 17 | operations = [ 18 | migrations.CreateModel( 19 | name='BlacklistedUser', 20 | fields=[ 21 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 22 | ('username', models.CharField(max_length=1000, unique=True)), 23 | ('date', models.DateTimeField(auto_now_add=True)), 24 | ('added_by', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), 25 | ], 26 | ), 27 | ] 28 | -------------------------------------------------------------------------------- /osmchadjango/supervise/migrations/0010_areaofinterest_date.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.11.1 on 2017-07-14 20:05 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | import django.utils.timezone 7 | 8 | 9 | class Migration(migrations.Migration): 10 | 11 | dependencies = [ 12 | ('supervise', '0009_blacklisteduser'), 13 | ] 14 | 15 | operations = [ 16 | migrations.AddField( 17 | model_name='areaofinterest', 18 | name='date', 19 | field=models.DateTimeField(auto_now_add=True, default=django.utils.timezone.now), 20 | preserve_default=False, 21 | ), 22 | ] 23 | -------------------------------------------------------------------------------- /osmchadjango/supervise/migrations/0011_auto_20170720_1914.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.11.1 on 2017-07-20 19:14 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('supervise', '0010_areaofinterest_date'), 12 | ] 13 | 14 | operations = [ 15 | migrations.AlterModelOptions( 16 | name='areaofinterest', 17 | options={'ordering': ['-date'], 'verbose_name': 'Area of Interest', 'verbose_name_plural': 'Areas of Interest'}, 18 | ), 19 | migrations.AddField( 20 | model_name='blacklisteduser', 21 | name='uid', 22 | field=models.CharField(max_length=255, null=True, unique=True), 23 | ), 24 | migrations.AlterField( 25 | model_name='blacklisteduser', 26 | name='username', 27 | field=models.CharField(max_length=1000), 28 | ), 29 | ] 30 | -------------------------------------------------------------------------------- /osmchadjango/supervise/migrations/0012_populate_uid_field.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import migrations 5 | 6 | 7 | def get_uid(apps, username): 8 | Changeset = apps.get_model('changeset', 'Changeset') 9 | last_changeset = Changeset.objects.filter(user=username).last() 10 | if last_changeset is not None: 11 | return Changeset.objects.filter(user=username).last().uid 12 | else: 13 | return None 14 | 15 | 16 | def populate_uid(apps, schema_editor): 17 | BlacklistedUser = apps.get_model('supervise', 'BlacklistedUser') 18 | for row in BlacklistedUser.objects.all(): 19 | row.uid = get_uid(apps, row.username) 20 | row.save(update_fields=['uid']) 21 | 22 | 23 | class Migration(migrations.Migration): 24 | 25 | dependencies = [ 26 | ('supervise', '0011_auto_20170720_1914'), 27 | ] 28 | 29 | operations = [ 30 | migrations.RunPython( 31 | populate_uid, 32 | reverse_code=migrations.RunPython.noop 33 | ), 34 | ] 35 | -------------------------------------------------------------------------------- /osmchadjango/supervise/migrations/0013_auto_20170720_1954.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.11.1 on 2017-07-20 19:54 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('supervise', '0012_populate_uid_field'), 12 | ] 13 | 14 | operations = [ 15 | migrations.AlterField( 16 | model_name='blacklisteduser', 17 | name='uid', 18 | field=models.CharField(max_length=255, unique=True), 19 | ), 20 | ] 21 | -------------------------------------------------------------------------------- /osmchadjango/supervise/migrations/0014_auto_20180307_1417.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.0.3 on 2018-03-07 14:17 2 | 3 | from django.db import migrations 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('supervise', '0013_auto_20170720_1954'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterModelOptions( 14 | name='blacklisteduser', 15 | options={'ordering': ['-date']}, 16 | ), 17 | ] 18 | -------------------------------------------------------------------------------- /osmchadjango/supervise/migrations/0015_auto_20181129_1135.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.0.9 on 2018-11-29 11:35 2 | 3 | from django.conf import settings 4 | from django.db import migrations, models 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | migrations.swappable_dependency(settings.AUTH_USER_MODEL), 11 | ('supervise', '0014_auto_20180307_1417'), 12 | ] 13 | 14 | operations = [ 15 | migrations.AlterField( 16 | model_name='blacklisteduser', 17 | name='uid', 18 | field=models.CharField(max_length=255), 19 | ), 20 | migrations.AlterUniqueTogether( 21 | name='blacklisteduser', 22 | unique_together={('uid', 'added_by')}, 23 | ), 24 | ] 25 | -------------------------------------------------------------------------------- /osmchadjango/supervise/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OSMCha/osmcha-django/8dfd85bedfc5389113f7e789f3ad9eab2609ca02/osmchadjango/supervise/migrations/__init__.py -------------------------------------------------------------------------------- /osmchadjango/supervise/models.py: -------------------------------------------------------------------------------- 1 | import uuid 2 | 3 | from django.contrib.gis.db import models 4 | from django.contrib.postgres.fields import JSONField 5 | from django.http.request import HttpRequest 6 | 7 | from osmchadjango.changeset.filters import ChangesetFilter 8 | 9 | from ..users.models import User 10 | 11 | 12 | class AreaOfInterest(models.Model): 13 | id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) 14 | name = models.CharField(max_length=255, blank=True) 15 | user = models.ForeignKey('users.User', on_delete=models.CASCADE) 16 | date = models.DateTimeField(auto_now_add=True) 17 | filters = JSONField() 18 | geometry = models.GeometryField(blank=True, null=True) 19 | 20 | def __str__(self): 21 | return '{} by {}'.format(self.name, self.user.username) 22 | 23 | def changesets(self, request=None): 24 | """Return the changesets that match the filters, including the geometry 25 | of the AreaOfInterest. Fake a request object in order to execute the 26 | query with the user that created the AoI, not with the request user. 27 | """ 28 | request = HttpRequest 29 | request.user = self.user 30 | qs = ChangesetFilter(self.filters, request=request).qs 31 | if self.geometry is not None: 32 | return qs.filter( 33 | bbox__intersects=self.geometry 34 | ) 35 | else: 36 | return qs 37 | 38 | class Meta: 39 | unique_together = ('user', 'name',) 40 | ordering = ['-date'] 41 | verbose_name = 'Area of Interest' 42 | verbose_name_plural = 'Areas of Interest' 43 | 44 | 45 | class BlacklistedUser(models.Model): 46 | username = models.CharField(max_length=1000) 47 | uid = models.CharField(max_length=255) 48 | added_by = models.ForeignKey(User, on_delete=models.CASCADE) 49 | date = models.DateTimeField(auto_now_add=True) 50 | 51 | def __str__(self): 52 | return self.uid 53 | 54 | def save(self, *args, **kwargs): 55 | self.full_clean() 56 | super(BlacklistedUser, self).save(*args, **kwargs) 57 | 58 | class Meta: 59 | unique_together = ('uid', 'added_by') 60 | ordering = ['-date'] 61 | -------------------------------------------------------------------------------- /osmchadjango/supervise/serializers.py: -------------------------------------------------------------------------------- 1 | from django.urls import reverse 2 | 3 | from rest_framework_gis.serializers import GeoFeatureModelSerializer 4 | from rest_framework_gis.fields import GeometryField 5 | from rest_framework.fields import ( 6 | SerializerMethodField, HiddenField, CurrentUserDefault, DateTimeField, 7 | ReadOnlyField 8 | ) 9 | from rest_framework.validators import ValidationError, UniqueTogetherValidator 10 | from rest_framework.serializers import ModelSerializer 11 | 12 | from .models import AreaOfInterest, BlacklistedUser 13 | 14 | 15 | class AreaOfInterestSerializer(GeoFeatureModelSerializer): 16 | changesets_url = SerializerMethodField() 17 | user = HiddenField(default=CurrentUserDefault()) 18 | geometry = GeometryField(read_only=True) 19 | 20 | class Meta: 21 | model = AreaOfInterest 22 | geo_field = 'geometry' 23 | fields = [ 24 | 'id', 'name', 'filters', 'geometry', 'date', 'changesets_url', 'user' 25 | ] 26 | validators = [ 27 | UniqueTogetherValidator( 28 | queryset=AreaOfInterest.objects.all(), 29 | fields=('name', 'user') 30 | ) 31 | ] 32 | 33 | def get_changesets_url(self, obj): 34 | return reverse('supervise:aoi-list-changesets', args=[obj.id]) 35 | 36 | def validate(self, data): 37 | if data.get('filters') is None and data.get('geometry') is None: 38 | raise ValidationError( 39 | 'Set a value to the filters field or to the geometry to be able to save the AoI' 40 | ) 41 | return data 42 | 43 | 44 | class AreaOfInterestAnonymousSerializer(AreaOfInterestSerializer): 45 | """Serializer to be used when an anonymous user access the AoI.""" 46 | def to_representation(self, instance): 47 | data = super( 48 | AreaOfInterestAnonymousSerializer, 49 | self 50 | ).to_representation(instance) 51 | for key in ['users', 'uids', 'checked_by']: 52 | if key in data['properties']['filters'].keys(): 53 | data['properties']['filters'].pop(key) 54 | 55 | return data 56 | 57 | 58 | class BlacklistSerializer(ModelSerializer): 59 | date = DateTimeField(read_only=True) 60 | added_by = ReadOnlyField(source='added_by.username') 61 | 62 | class Meta: 63 | model = BlacklistedUser 64 | fields = ('uid', 'username', 'date', 'added_by') 65 | -------------------------------------------------------------------------------- /osmchadjango/supervise/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OSMCha/osmcha-django/8dfd85bedfc5389113f7e789f3ad9eab2609ca02/osmchadjango/supervise/tests/__init__.py -------------------------------------------------------------------------------- /osmchadjango/supervise/urls.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import absolute_import, unicode_literals 3 | 4 | from django.urls import re_path 5 | from django.views.decorators.cache import cache_page 6 | 7 | from . import views 8 | 9 | 10 | app_name = 'supervise' 11 | urlpatterns = [ 12 | re_path( 13 | r'^aoi/$', 14 | view=views.AOIListCreateAPIView.as_view(), 15 | name='aoi-list-create' 16 | ), 17 | re_path( 18 | r'^aoi/(?P[0-9a-f-]+)/$', 19 | view=views.AOIRetrieveUpdateDestroyAPIView.as_view(), 20 | name='aoi-detail' 21 | ), 22 | re_path( 23 | r'^aoi/(?P[0-9a-f-]+)/changesets/$', 24 | view=views.AOIListChangesetsAPIView.as_view(), 25 | name='aoi-list-changesets' 26 | ), 27 | re_path( 28 | r'^aoi/(?P[0-9a-f-]+)/changesets/feed/$', 29 | view=cache_page(60 * 30)(views.AOIListChangesetsFeedView()), 30 | name='aoi-changesets-feed' 31 | ), 32 | re_path( 33 | r'^aoi/(?P[0-9a-f-]+)/stats/$', 34 | view=views.AOIStatsAPIView.as_view(), 35 | name='aoi-stats' 36 | ), 37 | re_path( 38 | r'^blacklisted-users/$', 39 | view=views.BlacklistedUserListCreateAPIView.as_view(), 40 | name='blacklist-list-create' 41 | ), 42 | re_path( 43 | r'^blacklisted-users/(?P[0-9a-f-]+)/$', 44 | view=views.BlacklistedUserDetailAPIView.as_view(), 45 | name='blacklist-detail' 46 | ), 47 | ] 48 | -------------------------------------------------------------------------------- /osmchadjango/templates/account/base.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% block title %}{% block head_title %}{% endblock head_title %}{% endblock title %} 3 | -------------------------------------------------------------------------------- /osmchadjango/templates/account/email.html: -------------------------------------------------------------------------------- 1 | {% extends "account/base.html" %} 2 | 3 | {% load i18n %} 4 | {% load url from future %} 5 | {% load crispy_forms_tags %} 6 | 7 | {% block head_title %}{% trans "Account" %}{% endblock %} 8 | 9 | {% block content %} 10 |
11 |
12 |
13 |

{% trans "E-mail Addresses" %}

14 | {% if user.emailaddress_set.all %} 15 |

{% trans 'The following e-mail addresses are associated with your account:' %}

16 | 17 | 46 | 47 | {% else %} 48 |

{% trans 'Warning:'%} {% trans "You currently do not have any e-mail address set up. You should really add an e-mail address so you can receive notifications, reset your password, etc." %}

49 | 50 | {% endif %} 51 | 52 | 53 |

{% trans "Add E-mail Address" %}

54 | 55 |
56 | {% csrf_token %} 57 | {{ form|crispy }} 58 | 59 |
60 |
61 |
62 |
63 | {% endblock %} 64 | 65 | 66 | {% block extra_body %} 67 | 80 | {% endblock %} 81 | -------------------------------------------------------------------------------- /osmchadjango/templates/account/email_confirm.html: -------------------------------------------------------------------------------- 1 | {% extends "account/base.html" %} 2 | 3 | {% load url from future %} 4 | {% load i18n %} 5 | {% load account %} 6 | 7 | {% block head_title %}{% trans "Confirm E-mail Address" %}{% endblock %} 8 | 9 | 10 | {% block content %} 11 |
12 |
13 |
14 |

{% trans "Confirm E-mail Address" %}

15 | 16 | {% if confirmation %} 17 | 18 | {% user_display confirmation.email_address.user as user_display %} 19 | 20 |

{% blocktrans with confirmation.email_address.email as email %}Please confirm that {{ email }} is an e-mail address for user {{ user_display }}.{% endblocktrans %}

21 | 22 |
23 | {% csrf_token %} 24 | 25 |
26 | 27 | {% else %} 28 | 29 | {% url 'account_email' as email_url %} 30 | 31 |

{% blocktrans %}This e-mail confirmation link expired or is invalid. Please issue a new e-mail confirmation request.{% endblocktrans %}

32 | 33 | {% endif %} 34 |
35 |
36 |
37 | {% endblock %} 38 | -------------------------------------------------------------------------------- /osmchadjango/templates/account/email_confirmed.html: -------------------------------------------------------------------------------- 1 | {% extends "account/base.html" %} 2 | 3 | {% load i18n %} 4 | {% load account %} 5 | 6 | {% block head_title %}{% trans "Confirm E-mail Address" %}{% endblock %} 7 | 8 | 9 | {% block content %} 10 |
11 |
12 |
13 |

{% trans "Confirm E-mail Address" %}

14 | 15 | {% user_display email_address.user as user_display %} 16 | 17 |

{% blocktrans with email_address.email as email %}You have confirmed that {{ email }} is an e-mail address for user {{ user_display }}.{% endblocktrans %}

18 |
19 |
20 |
21 | {% endblock %} 22 | -------------------------------------------------------------------------------- /osmchadjango/templates/account/login.html: -------------------------------------------------------------------------------- 1 | {% extends "account/base.html" %} 2 | 3 | {% load i18n %} 4 | {% load account %} 5 | {% load crispy_forms_tags %} 6 | 7 | {% block head_title %}{% trans "Sign In" %}{% endblock %} 8 | 9 | {% block content %} 10 |
11 |
12 |
13 |

{% trans "Sign In" %}

14 | 15 | {% if socialaccount.providers %} 16 |

{% blocktrans with site.name as site_name %}Please sign in with one 17 | of your existing third party accounts. Or, sign up 18 | for a {{ site_name }} account and sign in below:{% endblocktrans %}

19 | 20 |
21 | 22 |
    23 | {% include "socialaccount/snippets/provider_list.html" with process="login" %} 24 |
25 | 26 | 27 | 28 |
29 | 30 | {% include "socialaccount/snippets/login_extra.html" %} 31 | 32 | {% endif %} 33 | 34 | 43 |
44 |
45 |
46 | {% endblock %} 47 | 48 | -------------------------------------------------------------------------------- /osmchadjango/templates/account/logout.html: -------------------------------------------------------------------------------- 1 | {% extends "account/base.html" %} 2 | 3 | {% load i18n %} 4 | 5 | {% block head_title %}{% trans "Sign Out" %}{% endblock %} 6 | 7 | {% block content %} 8 |
9 |
10 |
11 | 12 |

{% trans "Sign Out" %}

13 | 14 |

{% trans 'Are you sure you want to sign out?' %}

15 | 16 |
17 | {% csrf_token %} 18 | {% if redirect_field_value %} 19 | 20 | {% endif %} 21 | 22 |
23 |
24 |
25 |
26 | 27 | {% endblock %} 28 | -------------------------------------------------------------------------------- /osmchadjango/templates/account/password_change.html: -------------------------------------------------------------------------------- 1 | {% extends "account/base.html" %} 2 | 3 | {% load i18n %} 4 | {% load crispy_forms_tags %} 5 | {% block head_title %}{% trans "Change Password" %}{% endblock %} 6 | 7 | {% block content %} 8 |
9 |
10 |
11 |

{% trans "Change Password" %}

12 | 13 |
14 | {% csrf_token %} 15 | {{ form|crispy }} 16 | 17 |
18 |
19 |
20 |
21 | {% endblock %} 22 | 23 | -------------------------------------------------------------------------------- /osmchadjango/templates/account/password_reset.html: -------------------------------------------------------------------------------- 1 | {% extends "account/base.html" %} 2 | 3 | {% load i18n %} 4 | {% load account %} 5 | {% load crispy_forms_tags %} 6 | 7 | {% block head_title %}{% trans "Password Reset" %}{% endblock %} 8 | 9 | {% block content %} 10 |
11 |
12 |
13 | 14 |

{% trans "Password Reset" %}

15 | {% if user.is_authenticated %} 16 | {% include "account/snippets/already_logged_in.html" %} 17 | {% endif %} 18 | 19 |

{% trans "Forgotten your password? Enter your e-mail address below, and we'll send you an e-mail allowing you to reset it." %}

20 | 21 |
22 | {% csrf_token %} 23 | {{ form|crispy }} 24 | 25 |
26 | 27 |

{% blocktrans %}Please contact us if you have any trouble resetting your password.{% endblocktrans %}

28 |
29 |
30 |
31 | {% endblock %} 32 | 33 | {% block javascript %} 34 | {{ block.super }} 35 | 38 | {% endblock javascript %} 39 | 40 | -------------------------------------------------------------------------------- /osmchadjango/templates/account/password_reset_done.html: -------------------------------------------------------------------------------- 1 | {% extends "account/base.html" %} 2 | 3 | {% load i18n %} 4 | {% load account %} 5 | 6 | {% block head_title %}{% trans "Password Reset" %}{% endblock %} 7 | 8 | {% block content %} 9 |
10 |
11 |
12 |

{% trans "Password Reset" %}

13 | 14 | {% if user.is_authenticated %} 15 | {% include "account/snippets/already_logged_in.html" %} 16 | {% endif %} 17 | 18 |

{% blocktrans %}We have sent you an e-mail. Please contact us if you do not receive it within a few minutes.{% endblocktrans %}

19 |
20 |
21 |
22 | {% endblock %} 23 | -------------------------------------------------------------------------------- /osmchadjango/templates/account/password_reset_from_key.html: -------------------------------------------------------------------------------- 1 | {% extends "account/base.html" %} 2 | 3 | {% load url from future %} 4 | {% load i18n %} 5 | {% load crispy_forms_tags %} 6 | 7 | {% block head_title %}{% trans "Change Password" %}{% endblock %} 8 | 9 | {% block content %} 10 |
11 |
12 |
13 |

{% if token_fail %}{% trans "Bad Token" %}{% else %}{% trans "Change Password" %}{% endif %}

14 | 15 | {% if token_fail %} 16 | {% url 'account_reset_password' as passwd_reset_url %} 17 |

{% blocktrans %}The password reset link was invalid, possibly because it has already been used. Please request a new password reset.{% endblocktrans %}

18 | {% else %} 19 | {% if form %} 20 |
21 | {% csrf_token %} 22 | {{ form|crispy }} 23 | 24 |
25 | {% else %} 26 |

{% trans 'Your password is now changed.' %}

27 | {% endif %} 28 | {% endif %} 29 |
30 |
31 |
32 | {% endblock %} 33 | 34 | -------------------------------------------------------------------------------- /osmchadjango/templates/account/password_reset_from_key_done.html: -------------------------------------------------------------------------------- 1 | {% extends "account/base.html" %} 2 | 3 | {% load url from future %} 4 | {% load i18n %} 5 | {% block head_title %}{% trans "Change Password" %}{% endblock %} 6 | 7 | {% block content %} 8 |
9 |
10 |
11 |

{% trans "Change Password" %}

12 |

{% trans 'Your password is now changed.' %}

13 |
14 |
15 |
16 | {% endblock %} 17 | -------------------------------------------------------------------------------- /osmchadjango/templates/account/password_set.html: -------------------------------------------------------------------------------- 1 | 2 | {% extends "account/base.html" %} 3 | 4 | {% load i18n crispy_forms_tags %} 5 | 6 | {% block head_title %}{% trans "Set Password" %}{% endblock %} 7 | 8 | {% block content %} 9 |
10 |
11 |
12 |

{% trans "Set Password" %}

13 | 14 |
15 | {% csrf_token %} 16 | {{ form|crispy }} 17 | 18 |
19 |
20 |
21 |
22 | {% endblock %} 23 | 24 | -------------------------------------------------------------------------------- /osmchadjango/templates/account/signup.html: -------------------------------------------------------------------------------- 1 | {% extends "account/base.html" %} 2 | 3 | {% load url from future %} 4 | {% load i18n %} 5 | {% load crispy_forms_tags %} 6 | 7 | {% block title %}{% trans "Signup" %}{% endblock title %} 8 | 9 | {% block content %} 10 |

{% trans "Sign Up" %}

11 | 12 |

{% blocktrans %}Already have an account? Then please sign in.{% endblocktrans %}

13 | 14 | 22 | 23 | 24 | {% endblock content %} 25 | -------------------------------------------------------------------------------- /osmchadjango/templates/account/signup_closed.html: -------------------------------------------------------------------------------- 1 | {% extends "account/base.html" %} 2 | 3 | {% load url from future %} 4 | {% load i18n %} 5 | 6 | {% block head_title %}{% trans "Sign Up Closed" %}{% endblock %} 7 | 8 | {% block content %} 9 |
10 |
11 |
12 |

{% trans "Sign Up Closed" %}

13 | 14 |

{% trans "We are sorry, but the sign up is currently closed." %}

15 |
16 |
17 |
18 | {% endblock %} 19 | -------------------------------------------------------------------------------- /osmchadjango/templates/account/verification_sent.html: -------------------------------------------------------------------------------- 1 | {% extends "account/base.html" %} 2 | 3 | {% load i18n %} 4 | 5 | {% block head_title %}{% trans "Verify Your E-mail Address" %}{% endblock %} 6 | 7 | {% block content %} 8 |
9 |
10 |
11 |

{% trans "Verify Your E-mail Address" %}

12 | 13 |

{% blocktrans %}We have sent an e-mail to {{ email }} for verification. Follow the link provided to finalize the signup process. Please contact us if you do not receive it within a few minutes.{% endblocktrans %}

14 |
15 |
16 |
17 | 18 | {% endblock %} 19 | -------------------------------------------------------------------------------- /osmchadjango/templates/account/verified_email_required.html: -------------------------------------------------------------------------------- 1 | {% extends "account/base.html" %} 2 | 3 | {% load url from future %} 4 | {% load i18n %} 5 | 6 | {% block head_title %}{% trans "Verify Your E-mail Address" %}{% endblock %} 7 | 8 | {% block content %} 9 |
10 |
11 |
12 |

{% trans "Verify Your E-mail Address" %}

13 | 14 | {% url 'account_email' as email_url %} 15 | 16 |

{% blocktrans %}This part of the site requires us to verify that 17 | you are who you claim to be. For this purpose, we require that you 18 | verify ownership of your e-mail address. {% endblocktrans %}

19 | 20 |

{% blocktrans %}We have sent an e-mail to you for 21 | verification. Please click on the link inside this e-mail. Please 22 | contact us if you do not receive it within a few minutes.{% endblocktrans %}

23 | 24 |

{% blocktrans %}Note: you can still change your e-mail address.{% endblocktrans %}

25 |
26 |
27 |
28 | {% endblock %} 29 | -------------------------------------------------------------------------------- /osmchadjango/templates/pages/about.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% load i18n %} 3 | 4 | {% block title %}{% trans 'About OSMCHA' %}{% endblock %} 5 | 6 | {% block content %} 7 | 8 |

9 | {% blocktrans %} 10 | OSMCHA is a tool to help identify OpenStreetMap changesets with a big number of changes or with suspect words like "import", "google", "waze", etc. 11 | Furthermore, it can be used as a peer revision system, any OSM user can verify the edits of another user. 12 | {% endblocktrans %} 13 |

14 |

15 | {% trans 'OSMCHA is Open Source. Collaborate with us on' %} GitHub. 16 |

17 | {% endblock content %} 18 | -------------------------------------------------------------------------------- /osmchadjango/templates/pages/home.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} -------------------------------------------------------------------------------- /osmchadjango/users/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | -------------------------------------------------------------------------------- /osmchadjango/users/admin.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import absolute_import, unicode_literals 3 | 4 | from django import forms 5 | from django.contrib import admin 6 | from django.contrib.auth.admin import UserAdmin as AuthUserAdmin 7 | from django.contrib.auth.forms import UserChangeForm, UserCreationForm 8 | 9 | from .models import User 10 | 11 | 12 | class MyUserChangeForm(UserChangeForm): 13 | class Meta(UserChangeForm.Meta): 14 | model = User 15 | 16 | 17 | class MyUserCreationForm(UserCreationForm): 18 | class Meta(UserCreationForm.Meta): 19 | model = User 20 | 21 | def clean_username(self): 22 | username = self.cleaned_data["username"] 23 | try: 24 | User.objects.get(username=username) 25 | except User.DoesNotExist: 26 | return username 27 | raise forms.ValidationError(self.error_messages['duplicate_username']) 28 | 29 | 30 | @admin.register(User) 31 | class UserAdmin(AuthUserAdmin): 32 | form = MyUserChangeForm 33 | add_form = MyUserCreationForm 34 | -------------------------------------------------------------------------------- /osmchadjango/users/management/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OSMCha/osmcha-django/8dfd85bedfc5389113f7e789f3ad9eab2609ca02/osmchadjango/users/management/__init__.py -------------------------------------------------------------------------------- /osmchadjango/users/management/commands/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OSMCha/osmcha-django/8dfd85bedfc5389113f7e789f3ad9eab2609ca02/osmchadjango/users/management/commands/__init__.py -------------------------------------------------------------------------------- /osmchadjango/users/management/commands/clear_tokens.py: -------------------------------------------------------------------------------- 1 | from django.core.management.base import BaseCommand 2 | 3 | from rest_framework.authtoken.models import Token 4 | 5 | 6 | class Command(BaseCommand): 7 | help = """Deletes all user Tokens.""" 8 | 9 | def handle(self, *args, **options): 10 | Token.objects.all().delete() 11 | print('All user Tokens were deleted.') 12 | -------------------------------------------------------------------------------- /osmchadjango/users/management/commands/update_user_names.py: -------------------------------------------------------------------------------- 1 | from django.core.management.base import BaseCommand 2 | 3 | from ...utils import update_user_name 4 | from ...models import User 5 | 6 | 7 | class Command(BaseCommand): 8 | help = """Update the name field of all users with they current username in 9 | OSM.""" 10 | 11 | def handle(self, *args, **options): 12 | [update_user_name(user) for user in User.objects.all()] 13 | print('Usernames updated.') 14 | -------------------------------------------------------------------------------- /osmchadjango/users/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import models, migrations 5 | import django.utils.timezone 6 | import django.contrib.auth.models 7 | import django.core.validators 8 | 9 | 10 | class Migration(migrations.Migration): 11 | 12 | dependencies = [ 13 | ('auth', '0006_require_contenttypes_0002'), 14 | ] 15 | 16 | operations = [ 17 | migrations.CreateModel( 18 | name='User', 19 | fields=[ 20 | ('id', models.AutoField(primary_key=True, verbose_name='ID', serialize=False, auto_created=True)), 21 | ('password', models.CharField(max_length=128, verbose_name='password')), 22 | ('last_login', models.DateTimeField(null=True, verbose_name='last login', blank=True)), 23 | ('is_superuser', models.BooleanField(help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status', default=False)), 24 | ('username', models.CharField(max_length=30, validators=[django.core.validators.RegexValidator('^[\\w.@+-]+$', 'Enter a valid username. This value may contain only letters, numbers and @/./+/-/_ characters.', 'invalid')], verbose_name='username', error_messages={'unique': 'A user with that username already exists.'}, help_text='Required. 30 characters or fewer. Letters, digits and @/./+/-/_ only.', unique=True)), 25 | ('first_name', models.CharField(max_length=30, verbose_name='first name', blank=True)), 26 | ('last_name', models.CharField(max_length=30, verbose_name='last name', blank=True)), 27 | ('email', models.EmailField(max_length=254, verbose_name='email address', blank=True)), 28 | ('is_staff', models.BooleanField(help_text='Designates whether the user can log into this admin site.', verbose_name='staff status', default=False)), 29 | ('is_active', models.BooleanField(help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.', verbose_name='active', default=True)), 30 | ('date_joined', models.DateTimeField(verbose_name='date joined', default=django.utils.timezone.now)), 31 | ('groups', models.ManyToManyField(related_name='user_set', blank=True, verbose_name='groups', to='auth.Group', help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', related_query_name='user')), 32 | ('user_permissions', models.ManyToManyField(related_name='user_set', blank=True, verbose_name='user permissions', to='auth.Permission', help_text='Specific permissions for this user.', related_query_name='user')), 33 | ('name', models.CharField(max_length=255, verbose_name='Name of User', blank=True)), 34 | ], 35 | options={ 36 | 'verbose_name': 'user', 37 | 'abstract': False, 38 | 'verbose_name_plural': 'users', 39 | }, 40 | managers=[ 41 | ('objects', django.contrib.auth.models.UserManager()), 42 | ], 43 | ), 44 | ] 45 | -------------------------------------------------------------------------------- /osmchadjango/users/migrations/0002_auto_20151124_0124.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import migrations, models 5 | import django.contrib.auth.models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('users', '0001_initial'), 12 | ] 13 | 14 | operations = [ 15 | migrations.AlterModelManagers( 16 | name='user', 17 | managers=[ 18 | ('objects', django.contrib.auth.models.UserManager()), 19 | ], 20 | ), 21 | ] 22 | -------------------------------------------------------------------------------- /osmchadjango/users/migrations/0002_auto_20161005_1234.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.10 on 2016-10-05 12:34 3 | from __future__ import unicode_literals 4 | 5 | import django.contrib.auth.validators 6 | from django.db import migrations, models 7 | 8 | 9 | class Migration(migrations.Migration): 10 | 11 | dependencies = [ 12 | ('users', '0001_initial'), 13 | ] 14 | 15 | operations = [ 16 | migrations.AlterField( 17 | model_name='user', 18 | name='username', 19 | field=models.CharField(error_messages={'unique': 'A user with that username already exists.'}, help_text='Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.', max_length=150, unique=True, validators=[django.contrib.auth.validators.ASCIIUsernameValidator()], verbose_name='username'), 20 | ), 21 | ] 22 | -------------------------------------------------------------------------------- /osmchadjango/users/migrations/0003_merge_20170130_1414.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.10 on 2017-01-30 14:14 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('users', '0002_auto_20151124_0124'), 12 | ('users', '0002_auto_20161005_1234'), 13 | ] 14 | 15 | operations = [ 16 | ] 17 | -------------------------------------------------------------------------------- /osmchadjango/users/migrations/0004_auto_20170201_1617.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.10 on 2017-02-01 16:17 3 | from __future__ import unicode_literals 4 | 5 | import django.contrib.auth.validators 6 | from django.db import migrations, models 7 | 8 | 9 | class Migration(migrations.Migration): 10 | 11 | dependencies = [ 12 | ('users', '0003_merge_20170130_1414'), 13 | ] 14 | 15 | operations = [ 16 | migrations.AlterField( 17 | model_name='user', 18 | name='username', 19 | field=models.CharField(error_messages={'unique': 'A user with that username already exists.'}, help_text='Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.', max_length=150, unique=True, validators=[django.contrib.auth.validators.UnicodeUsernameValidator()], verbose_name='username'), 20 | ), 21 | ] 22 | -------------------------------------------------------------------------------- /osmchadjango/users/migrations/0005_auto_20170328_1352.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.10 on 2017-03-28 13:52 3 | from __future__ import unicode_literals 4 | 5 | import django.contrib.auth.validators 6 | from django.db import migrations, models 7 | 8 | 9 | class Migration(migrations.Migration): 10 | 11 | dependencies = [ 12 | ('users', '0004_auto_20170201_1617'), 13 | ] 14 | 15 | operations = [ 16 | migrations.AlterField( 17 | model_name='user', 18 | name='username', 19 | field=models.CharField(error_messages={'unique': 'A user with that username already exists.'}, help_text='Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.', max_length=150, unique=True, validators=[django.contrib.auth.validators.ASCIIUsernameValidator()], verbose_name='username'), 20 | ), 21 | ] 22 | -------------------------------------------------------------------------------- /osmchadjango/users/migrations/0006_auto_20170330_1401.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.10 on 2017-03-30 14:01 3 | from __future__ import unicode_literals 4 | 5 | import django.contrib.auth.validators 6 | from django.db import migrations, models 7 | 8 | 9 | class Migration(migrations.Migration): 10 | 11 | dependencies = [ 12 | ('users', '0005_auto_20170328_1352'), 13 | ] 14 | 15 | operations = [ 16 | migrations.AlterField( 17 | model_name='user', 18 | name='username', 19 | field=models.CharField(error_messages={'unique': 'A user with that username already exists.'}, help_text='Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.', max_length=150, unique=True, validators=[django.contrib.auth.validators.UnicodeUsernameValidator()], verbose_name='username'), 20 | ), 21 | ] 22 | -------------------------------------------------------------------------------- /osmchadjango/users/migrations/0007_auto_20170421_1939.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.10.7 on 2017-04-21 19:39 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('users', '0006_auto_20170330_1401'), 12 | ] 13 | 14 | operations = [ 15 | migrations.AlterField( 16 | model_name='user', 17 | name='name', 18 | field=models.CharField(blank=True, db_index=True, max_length=255, verbose_name='Name of User'), 19 | ), 20 | ] 21 | -------------------------------------------------------------------------------- /osmchadjango/users/migrations/0008_auto_20180118_1229.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.11.8 on 2018-01-18 12:29 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('users', '0007_auto_20170421_1939'), 12 | ] 13 | 14 | operations = [ 15 | migrations.AddField( 16 | model_name='user', 17 | name='comment_feature', 18 | field=models.BooleanField(default=False), 19 | ), 20 | migrations.AddField( 21 | model_name='user', 22 | name='message_bad', 23 | field=models.TextField(blank=True, verbose_name='Default message to bad changesets'), 24 | ), 25 | migrations.AddField( 26 | model_name='user', 27 | name='message_good', 28 | field=models.TextField(blank=True, verbose_name='Default message to good changesets'), 29 | ), 30 | ] 31 | -------------------------------------------------------------------------------- /osmchadjango/users/migrations/0009_auto_20180307_1417.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.0.3 on 2018-03-07 14:17 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('users', '0008_auto_20180118_1229'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterField( 14 | model_name='user', 15 | name='comment_feature', 16 | field=models.BooleanField(default=False, verbose_name='Enable suggestion to post comment'), 17 | ), 18 | migrations.AlterField( 19 | model_name='user', 20 | name='last_name', 21 | field=models.CharField(blank=True, max_length=150, verbose_name='last name'), 22 | ), 23 | ] 24 | -------------------------------------------------------------------------------- /osmchadjango/users/migrations/0010_auto_20190123_1129.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.0.9 on 2019-01-23 11:29 2 | 3 | from django.conf import settings 4 | import django.contrib.postgres.fields.jsonb 5 | from django.db import migrations, models 6 | import django.db.models.deletion 7 | 8 | 9 | class Migration(migrations.Migration): 10 | 11 | dependencies = [ 12 | ('users', '0009_auto_20180307_1417'), 13 | ] 14 | 15 | operations = [ 16 | migrations.CreateModel( 17 | name='MappingTeam', 18 | fields=[ 19 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 20 | ('name', models.CharField(db_index=True, max_length=255)), 21 | ('date', models.DateTimeField(auto_now_add=True, verbose_name='Creation Date')), 22 | ('trusted', models.BooleanField(default=False)), 23 | ('users', django.contrib.postgres.fields.jsonb.JSONField(default=list)), 24 | ('created_by', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), 25 | ], 26 | options={ 27 | 'verbose_name': 'Mapping Team', 28 | 'verbose_name_plural': 'Mapping Teams', 29 | 'ordering': ['date'], 30 | }, 31 | ), 32 | migrations.AlterUniqueTogether( 33 | name='mappingteam', 34 | unique_together={('created_by', 'name')}, 35 | ), 36 | ] 37 | -------------------------------------------------------------------------------- /osmchadjango/users/migrations/0011_auto_20190206_1257.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.0.10 on 2019-02-06 12:57 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('users', '0010_auto_20190123_1129'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterField( 14 | model_name='mappingteam', 15 | name='name', 16 | field=models.CharField(db_index=True, max_length=255, unique=True), 17 | ), 18 | migrations.AlterUniqueTogether( 19 | name='mappingteam', 20 | unique_together=set(), 21 | ), 22 | ] 23 | -------------------------------------------------------------------------------- /osmchadjango/users/migrations/0012_auto_20240523_0227.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2.28 on 2024-05-23 02:27 2 | 3 | from django.db import migrations 4 | 5 | 6 | def update_provider(apps, schema_editor): 7 | # To keep the same users entries we have right now in the database, 8 | # we need to update the UserSocialAuth entries' providers 9 | UserSocialAuth = apps.get_model("social_django", "UserSocialAuth") 10 | UserSocialAuth.objects.all().update(provider="openstreetmap-oauth2") 11 | 12 | 13 | class Migration(migrations.Migration): 14 | 15 | dependencies = [ 16 | ('users', '0011_auto_20190206_1257'), 17 | ] 18 | 19 | operations = [ 20 | migrations.RunPython(update_provider), 21 | ] 22 | -------------------------------------------------------------------------------- /osmchadjango/users/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OSMCha/osmcha-django/8dfd85bedfc5389113f7e789f3ad9eab2609ca02/osmchadjango/users/migrations/__init__.py -------------------------------------------------------------------------------- /osmchadjango/users/models.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals, absolute_import 3 | 4 | from django.contrib.auth.models import AbstractUser 5 | from django.db import models 6 | from django.contrib.postgres.fields import JSONField 7 | 8 | 9 | class User(AbstractUser): 10 | # First Name and Last Name do not cover name patterns around the globe. 11 | name = models.CharField( 12 | "Name of User", blank=True, max_length=255, db_index=True 13 | ) 14 | message_good = models.TextField( 15 | "Default message to good changesets", 16 | blank=True 17 | ) 18 | message_bad = models.TextField( 19 | "Default message to bad changesets", 20 | blank=True 21 | ) 22 | comment_feature = models.BooleanField( 23 | "Enable suggestion to post comment", 24 | default=False 25 | ) 26 | 27 | def __str__(self): 28 | return self.username 29 | 30 | 31 | class MappingTeam(models.Model): 32 | name = models.CharField(max_length=255, db_index=True, unique=True) 33 | date = models.DateTimeField("Creation Date", auto_now_add=True) 34 | trusted = models.BooleanField(default=False) 35 | users = JSONField(default=list) 36 | created_by = models.ForeignKey(User, on_delete=models.CASCADE) 37 | 38 | def __str__(self): 39 | return '{} by {}'.format(self.name, self.created_by.username) 40 | 41 | class Meta: 42 | ordering = ['date'] 43 | verbose_name = 'Mapping Team' 44 | verbose_name_plural = 'Mapping Teams' 45 | -------------------------------------------------------------------------------- /osmchadjango/users/serializers.py: -------------------------------------------------------------------------------- 1 | from django.contrib.auth import get_user_model 2 | 3 | from rest_framework.serializers import ( 4 | ModelSerializer, Serializer, CharField, SlugRelatedField 5 | ) 6 | from rest_framework.fields import ReadOnlyField 7 | from rest_framework.fields import SerializerMethodField 8 | 9 | from .models import MappingTeam 10 | 11 | 12 | class UserSerializer(ModelSerializer): 13 | uid = SerializerMethodField() 14 | avatar = SerializerMethodField() 15 | username = SerializerMethodField() 16 | whitelists = SlugRelatedField( 17 | many=True, 18 | read_only=True, 19 | slug_field='whitelist_user' 20 | ) 21 | 22 | def get_uid(self, obj): 23 | try: 24 | return obj.social_auth.filter(provider='openstreetmap-oauth2').last().uid 25 | except AttributeError: 26 | return None 27 | 28 | def get_avatar(self, obj): 29 | try: 30 | return obj.social_auth.filter( 31 | provider='openstreetmap-oauth2' 32 | ).last().extra_data.get('avatar') 33 | except AttributeError: 34 | return None 35 | 36 | def get_username(self, obj): 37 | if obj.name: 38 | return obj.name 39 | else: 40 | return obj.username 41 | 42 | class Meta: 43 | model = get_user_model() 44 | fields = ( 45 | 'id', 'uid', 'username', 'is_staff', 'is_active', 'email', 46 | 'avatar', 'whitelists', 'message_good', 'message_bad', 47 | 'comment_feature' 48 | ) 49 | read_only_fields = ('username', 'is_staff', 'is_active', 'id', 'uid') 50 | 51 | 52 | class SocialSignUpSerializer(Serializer): 53 | code = CharField() 54 | 55 | 56 | class MappingTeamSerializer(ModelSerializer): 57 | owner = ReadOnlyField(source='created_by.username', default=None) 58 | 59 | class Meta: 60 | model = MappingTeam 61 | fields = ('id', 'name', 'users', 'trusted', 'owner') 62 | read_only_fields = ('trusted', 'owner') 63 | -------------------------------------------------------------------------------- /osmchadjango/users/tests/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | -------------------------------------------------------------------------------- /osmchadjango/users/tests/test_management.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.test import TestCase 5 | from django.core.management import call_command 6 | 7 | from social_django.models import UserSocialAuth 8 | from rest_framework.authtoken.models import Token 9 | 10 | from ..models import User 11 | 12 | 13 | class TestUpdateUserNameCommand(TestCase): 14 | def setUp(self): 15 | self.user = User.objects.create_user( 16 | username='rosrever', 17 | email='b@a.com', 18 | password='password' 19 | ) 20 | UserSocialAuth.objects.create( 21 | user=self.user, 22 | provider='openstreetmap-oauth2', 23 | uid='5654409', 24 | ) 25 | self.user_2 = User.objects.create_user( 26 | username='NarcéliodeSá', 27 | email='c@a.com', 28 | password='password' 29 | ) 30 | UserSocialAuth.objects.create( 31 | user=self.user_2, 32 | provider='openstreetmap-oauth2', 33 | uid='612405', 34 | ) 35 | 36 | def test_command(self): 37 | call_command('update_user_names') 38 | self.user.refresh_from_db() 39 | self.user_2.refresh_from_db() 40 | self.assertEqual(self.user.name, 'rosrever') 41 | self.assertEqual(self.user_2.name, 'narceliodesa') 42 | 43 | 44 | class TestClearTokensCommand(TestCase): 45 | def setUp(self): 46 | self.user = User.objects.create_user( 47 | username='rosrever', 48 | email='b@a.com', 49 | password='password' 50 | ) 51 | UserSocialAuth.objects.create( 52 | user=self.user, 53 | provider='openstreetmap-oauth2', 54 | uid='5654409', 55 | ) 56 | Token.objects.create(user=self.user) 57 | 58 | def test_command(self): 59 | self.assertEqual(Token.objects.count(), 1) 60 | call_command('clear_tokens') 61 | self.assertEqual(Token.objects.count(), 0) 62 | -------------------------------------------------------------------------------- /osmchadjango/users/tests/test_utils.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.test import TestCase 5 | 6 | from social_django.models import UserSocialAuth 7 | 8 | from ..models import User 9 | from ..utils import update_user_name 10 | 11 | 12 | class TestUpdateUserName(TestCase): 13 | def setUp(self): 14 | self.user = User.objects.create_user( 15 | username='rosrever', 16 | email='b@a.com', 17 | password='password' 18 | ) 19 | UserSocialAuth.objects.create( 20 | user=self.user, 21 | provider='openstreetmap-oauth2', 22 | uid='5654409', 23 | ) 24 | self.user_2 = User.objects.create_user( 25 | username='NarcéliodeSá', 26 | email='c@a.com', 27 | password='password' 28 | ) 29 | UserSocialAuth.objects.create( 30 | user=self.user_2, 31 | provider='openstreetmap-oauth2', 32 | uid='612405', 33 | ) 34 | 35 | def test_update_user_name(self): 36 | update_user_name(self.user) 37 | update_user_name(self.user_2) 38 | self.assertEqual(self.user.name, 'rosrever') 39 | self.assertEqual(self.user_2.name, 'narceliodesa') 40 | 41 | def test_user_with_wrong_uid(self): 42 | user = User.objects.create_user( 43 | username='test', 44 | email='d@a.com', 45 | password='password' 46 | ) 47 | UserSocialAuth.objects.create( 48 | user=user, 49 | provider='openstreetmap-oauth2', 50 | uid='1989776798', 51 | ) 52 | update_user_name(user) 53 | self.assertEqual(user.name, '') 54 | 55 | def test_user_without_social_auth(self): 56 | user = User.objects.create_user( 57 | username='test', 58 | email='d@a.com', 59 | password='password' 60 | ) 61 | update_user_name(user) 62 | self.assertEqual(user.name, '') 63 | -------------------------------------------------------------------------------- /osmchadjango/users/urls.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import absolute_import, unicode_literals 3 | 4 | from django.urls import re_path 5 | 6 | from . import views 7 | 8 | 9 | app_name = 'users' 10 | urlpatterns = [ 11 | re_path( 12 | r'^users/$', 13 | view=views.CurrentUserDetailAPIView.as_view(), 14 | name='detail' 15 | ), 16 | re_path( 17 | r'^social-auth/$', 18 | view=views.SocialAuthAPIView.as_view(), 19 | name="social-auth" 20 | ), 21 | re_path( 22 | r'^update-deleted-users/$', 23 | view=views.update_deleted_users, 24 | name="update-deleted-users" 25 | ), 26 | re_path( 27 | r'^mapping-team/$', 28 | views.MappingTeamListCreateAPIView.as_view(), 29 | name="mapping-team" 30 | ), 31 | re_path( 32 | r'^mapping-team/(?P\d+)/$', 33 | views.MappingTeamDetailAPIView.as_view(), 34 | name="mapping-team-detail" 35 | ), 36 | re_path( 37 | r'^mapping-team/(?P\d+)/trust/$', 38 | view=views.MappingTeamTrustingAPIView.as_view({'put': 'set_trusted'}), 39 | name='trust-mapping-team' 40 | ), 41 | re_path( 42 | r'^mapping-team/(?P\d+)/untrust/$', 43 | view=views.MappingTeamTrustingAPIView.as_view({'put': 'set_untrusted'}), 44 | name='untrust-mapping-team' 45 | ), 46 | ] 47 | -------------------------------------------------------------------------------- /osmchadjango/users/utils.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from django.conf import settings 3 | 4 | from social_django.models import UserSocialAuth 5 | 6 | import requests 7 | 8 | 9 | def save_real_username(backend, user, response, *args, **kwargs): 10 | """Function created to be part of the social_auth authentication pipeline. 11 | It records the real username of the OSM user in the name field of the User 12 | model. 13 | """ 14 | if backend.name == 'openstreetmap-oauth2' and user.name != response.get('username'): 15 | user.name = response.get('username') 16 | user.save(update_fields=['name']) 17 | 18 | 19 | def update_user_name(user): 20 | """Get the current username of an OSM user and update the name field of this 21 | user in our local database. 22 | """ 23 | try: 24 | uid = user.social_auth.get(provider='openstreetmap-oauth2').uid 25 | url = f'{settings.OSM_SERVER_URL}/api/0.6/user/{uid}.json' 26 | data = requests.get(url, headers=settings.OSM_API_USER_AGENT).json() 27 | display_name = data['user']['display_name'] 28 | if user.name != display_name: 29 | user.name = display_name 30 | user.save(update_fields=['name']) 31 | print('User with uid {} updated successfully.'.format(uid)) 32 | except UserSocialAuth.DoesNotExist: 33 | print( 34 | 'User {} does not have a social_auth instance.'.format(user.username) 35 | ) 36 | except requests.exceptions.JSONDecodeError: 37 | print('It was not possible to update user with uid {}.'.format(uid)) 38 | -------------------------------------------------------------------------------- /requirements.apt: -------------------------------------------------------------------------------- 1 | ##basic build dependencies of various Django apps for Ubuntu 14.04 2 | #build-essential metapackage install: make, gcc, g++, 3 | build-essential 4 | #required to translate 5 | gettext 6 | python-dev 7 | 8 | ##shared dependencies of: 9 | ##Pillow, pylibmc 10 | zlib1g-dev 11 | 12 | ##Postgresql and psycopg2 dependencies 13 | libpq-dev 14 | 15 | ##django-extensions 16 | graphviz-dev 17 | 18 | # GeoDjango 19 | binutils 20 | libproj-dev 21 | gdal-bin 22 | 23 | # osmcha / homura 24 | curl 25 | libcurl4-openssl-dev 26 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | # This file is here because many Platforms as a Service look for 2 | # requirements.txt in the root directory of a project. 3 | -r requirements/production.txt 4 | -------------------------------------------------------------------------------- /requirements/base.txt: -------------------------------------------------------------------------------- 1 | # Bleeding edge Django 2 | 3 | django==2.2.28 4 | 5 | # Configuration 6 | django-environ==0.4.5 7 | 8 | # For authentication with OSM 9 | social-auth-app-django==5.1.0 10 | social-auth-core==4.5.4 11 | 12 | # DRF and DRF-gis 13 | djangorestframework==3.11.2 14 | djangorestframework-gis==1.0.0 15 | djangorestframework-csv==2.1.1 16 | django-filter==2.4.0 17 | django-cors-headers==3.7.0 18 | drf-yasg==1.21.5 19 | 20 | # For the persistence stores 21 | psycopg2-binary>=2.8,<2.9 22 | 23 | # Unicode slugification 24 | unicode-slugify==0.1.3 25 | django-autoslug==1.9.8 26 | 27 | # Time zones support 28 | pytz==2024.1 29 | 30 | # Redis support 31 | django-redis==4.11.0 32 | redis>=2.10.5 33 | 34 | # Your custom requirements go here 35 | PyYAML==6.0.2 36 | osmcha==0.9.2 37 | -------------------------------------------------------------------------------- /requirements/local.txt: -------------------------------------------------------------------------------- 1 | # Local development dependencies go here 2 | -r test.txt 3 | django-extensions==3.1.5 4 | Sphinx==4.3.2 5 | Werkzeug==1.0.1 6 | 7 | # django-debug-toolbar that works with Django 1.5+ 8 | django-debug-toolbar==3.2.4 9 | sqlparse==0.4.4 10 | 11 | #shell 12 | bpython==0.21 13 | -------------------------------------------------------------------------------- /requirements/production.txt: -------------------------------------------------------------------------------- 1 | # Pro-tip: Try not to put anything here. There should be no dependency in 2 | # production that isn't in development. 3 | -r base.txt 4 | 5 | gevent==23.9.1 6 | gunicorn==21.2.0 7 | -------------------------------------------------------------------------------- /requirements/test.txt: -------------------------------------------------------------------------------- 1 | # Test dependencies go here. 2 | -r base.txt 3 | flake8==4.0.1 4 | coverage==5.5 5 | factory-boy==3.2.1 6 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [flake8] 2 | #ignore = E265 3 | max-line-length = 120 4 | exclude = .tox,.git,*/migrations/*,*/tests/*,*/static/CACHE/*,docs 5 | 6 | [coverage:run] 7 | source = 8 | osmchadjango/changeset 9 | osmchadjango/supervise 10 | osmchadjango/users 11 | osmchadjango/roulette_integration 12 | omit = 13 | */migrations/* 14 | --------------------------------------------------------------------------------