diff --git a/.flake8 b/.flake8 index 01572a25..9a26df21 100644 --- a/.flake8 +++ b/.flake8 @@ -2,8 +2,6 @@ exclude = */docs/*,*/.tox/*,*/.venv/*,*/.pycharm_helpers/*,*/migrations/*,docs/*,*/__init__.py, */manage.py,*/wsgi.py,*/django.wsgi, # we don't check this for now, until it's been fixed. otherwise it will throw lot of errors - qgis-app/plugins/*, - qgis-app/userexport/*, qgis-app/lib/templatetags/*, vagrant_assets/*, qgis-app/users/*, diff --git a/.github/workflows/build_push_image.yml b/.github/workflows/build_push_image.yml index b242a596..e45a3000 100644 --- a/.github/workflows/build_push_image.yml +++ b/.github/workflows/build_push_image.yml @@ -29,8 +29,8 @@ jobs: file: dockerize/docker/Dockerfile push: true tags: | - qgis/qgis-plugins-uwsgi:${{ github.ref_name }} - qgis/qgis-plugins-uwsgi:latest + qgis/qgis-hub-uwsgi:${{ github.ref_name }} + qgis/qgis-hub-uwsgi:latest - name: Log out from Docker Hub run: docker logout diff --git a/INSTALL.md b/INSTALL.md index 74180db7..4309a633 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -36,12 +36,6 @@ $ make devweb-runserver ``` and now, you can see your site at `http://0.0.0.0:62202` (skip this step if you are using PyCharm interpreter) -- If 'None' appears in the search results, it indicates a misalignment between the search index and the database. This discrepancy often arises when a plugin is deleted from the model but persists in the search index. To rectify this issue, it is essential to synchronize the search index with the database by rebuilding it. Execute the following command to initiate the rebuilding process: - -```bash -$ make rebuild_index -``` -This command ensures that the search index accurately reflects the current state of the database, resolving the presence of 'None' in the search results. Automatic synchronization is currently managed in settings.py: `HAYSTACK_SIGNAL_PROCESSOR = "haystack.signals.RealtimeSignalProcessor"`. For more information about make commands, please see the full docs [here](./dockerize/README.md). @@ -108,7 +102,7 @@ Now set these options: * **Run browser** If checked, it will open the url after you click run. You should be able to access the running on 0.0.0.0:62202 (the port that mapped to 8080) * **Environment vars** , you can add the variables value one-by-one by clicking on browse icon at right corner in the input field, or just copy-paste this value: -`PYTHONUNBUFFERED=1;DJANGO_SETTINGS_MODULE=settings_docker;RABBITMQ_HOST=rabbitmq;DATABASE_NAME=gis;DATABASE_USERNAME=docker;DATABASE_PASSWORD=docker;DATABASE_HOST=db` +`PYTHONUNBUFFERED=1;DJANGO_SETTINGS_MODULE=settings_docker;DATABASE_NAME=gis;DATABASE_USERNAME=docker;DATABASE_PASSWORD=docker;DATABASE_HOST=db` * **Python interpreter:** Ensure it is set you your remote interpreter (should be set to that by default) @@ -165,31 +159,31 @@ backups ├── 2019 ├── 2020 │   ├── April -│   │   └── PG_QGIS_PLUGINS_gis.07-April-2020.dmp +│   │   └── PG_QGIS_HUB_gis.07-April-2020.dmp │   ├── August │   ├── December -│   │   ├── PG_QGIS_PLUGINS_gis.01-December-2020.dmp -│   │   ├── PG_QGIS_PLUGINS_gis.02-December-2020.dmp -│   │   ├── PG_QGIS_PLUGINS_gis.03-December-2020.dmp -│   │   ├── PG_QGIS_PLUGINS_gis.04-December-2020.dmp -│   │   ├── PG_QGIS_PLUGINS_gis.05-December-2020.dmp -│   │   ├── PG_QGIS_PLUGINS_gis.06-December-2020.dmp -│   │   ├── PG_QGIS_PLUGINS_gis.07-December-2020.dmp -│   │   ├── PG_QGIS_PLUGINS_gis.08-December-2020.dmp -│   │   ├── PG_QGIS_PLUGINS_gis.09-December-2020.dmp -│   │   ├── PG_QGIS_PLUGINS_gis.10-December-2020.dmp -│   │   ├── PG_QGIS_PLUGINS_gis.11-December-2020.dmp -│   │   ├── PG_QGIS_PLUGINS_gis.12-December-2020.dmp -│   │   ├── PG_QGIS_PLUGINS_gis.13-December-2020.dmp -│   │   ├── PG_QGIS_PLUGINS_gis.14-December-2020.dmp -│   │   ├── PG_QGIS_PLUGINS_gis.15-December-2020.dmp -│   │   └── PG_QGIS_PLUGINS_gis.16-December-2020.dmp +│   │   ├── PG_QGIS_HUB_gis.01-December-2020.dmp +│   │   ├── PG_QGIS_HUB_gis.02-December-2020.dmp +│   │   ├── PG_QGIS_HUB_gis.03-December-2020.dmp +│   │   ├── PG_QGIS_HUB_gis.04-December-2020.dmp +│   │   ├── PG_QGIS_HUB_gis.05-December-2020.dmp +│   │   ├── PG_QGIS_HUB_gis.06-December-2020.dmp +│   │   ├── PG_QGIS_HUB_gis.07-December-2020.dmp +│   │   ├── PG_QGIS_HUB_gis.08-December-2020.dmp +│   │   ├── PG_QGIS_HUB_gis.09-December-2020.dmp +│   │   ├── PG_QGIS_HUB_gis.10-December-2020.dmp +│   │   ├── PG_QGIS_HUB_gis.11-December-2020.dmp +│   │   ├── PG_QGIS_HUB_gis.12-December-2020.dmp +│   │   ├── PG_QGIS_HUB_gis.13-December-2020.dmp +│   │   ├── PG_QGIS_HUB_gis.14-December-2020.dmp +│   │   ├── PG_QGIS_HUB_gis.15-December-2020.dmp +│   │   └── PG_QGIS_HUB_gis.16-December-2020.dmp │` ``` - Copy the dump file you wish to restore to dockerize/backups/latest.dmp file ```bash -$ cp backups/2020/December/PG_QGIS_PLUGINS_gis.16-December-2020.dmp dockerize/backups/latest.dmp +$ cp backups/2020/December/PG_QGIS_HUB_gis.16-December-2020.dmp dockerize/backups/latest.dmp ``` - Restore the dump file diff --git a/Makefile b/Makefile deleted file mode 100644 index 9030f068..00000000 --- a/Makefile +++ /dev/null @@ -1,58 +0,0 @@ -SHELL := /bin/bash - -# Override this vars on the command line: -# Example: -# $ make DB_OPTS="-h 10.96.0.167 -U dbuser" DB_NAME="dbname" - -DB_NAME=qgis-django -DB_OPTS= -PRJ_DIR=./qgis - -all: - @echo "See Makefile source for targets details" - - -run: kill_server - cd $(PRJ_DIR) && python manage.py runserver 0.0.0.0:8000 - -kill_server: - @if /usr/sbin/lsof -i :8000; then \ - echo "WARNING: A server was already listening on port 8000, I'm trying to kill it"; \ - kill -9 `/usr/sbin/lsof -i :8000 -Fp|cut -c2-`; \ - fi - -clean: - find $(PRJ_DIR) -type f -name "*.pyc" -exec rm -rf \{\} \; - -cleardb: clean - -dropdb $(DB_NAME) $(DB_OPTS) - createdb $(DB_NAME) $(DB_OPTS) -E UTF-8 -T template_postgis - - -reset: cleardb - cd $(PRJ_DIR) && python manage.py syncdb --noinput - cd $(PRJ_DIR) && python manage.py loaddata fixtures/auth.json - @echo 'Login as admin/admin' - - -runlocal: kill_server - cd $(PRJ_DIR) && python manage.py runserver 0.0.0.0:8000 - -shell: - cd $(PRJ_DIR) && python manage.py shell --plain - -ishell: - cd $(PRJ_DIR) && python manage.py shell - - -dumpauth: - cd $(PRJ_DIR) && python manage.py dumpdata --format=json --indent=4 auth.user > fixtures/auth.json - -dumpplugins: - cd $(PRJ_DIR) && python manage.py dumpdata --format=json --indent=4 plugins.plugin plugins.pluginversion > fixtures/plugins.json - -loadplugins: - cd $(PRJ_DIR) && python manage.py loaddata fixtures/plugins.json - -check: - $(MAKE) -C qgis $@ diff --git a/Vagrantfile b/Vagrantfile deleted file mode 100644 index 041bd5c6..00000000 --- a/Vagrantfile +++ /dev/null @@ -1,34 +0,0 @@ -# -*- mode: ruby -*- -# vi: set ft=ruby : - -#BOX = "bionic-canonical-64" -#BOX_URL = "https://cloud-images.ubuntu.com/bionic/current/bionic-server-cloudimg-amd64-vagrant.box" -BOX = "debian/stretch64" - -Vagrant.configure("2") do |config| - config.vm.box = BOX - #config.vm.box_url = BOX_URL - #config.disksize.size = '20GB' - - config.vm.network "forwarded_port", guest: 80, host: 8080 # nginx fastcgi - config.vm.post_up_message = "Plugin site is available on http://localhost:8080" - - config.vm.provider "virtualbox" do |v| - #v.gui = true - v.memory = 8192 - v.cpus = 3 - v.customize ["modifyvm", :id, "--vram", "128"] - end - - # Install the required software - config.vm.provision "shell", - path: "vagrant_assets/provision_setup.sh", - args: ENV['SHELL_ARGS'] - - # Run every time the VM starts - config.vm.provision "shell", - path: "vagrant_assets/provision_job.sh", - args: ENV['SHELL_ARGS'], - run: "always" - -end diff --git a/apache/apache.virtenv.conf.example b/apache/apache.virtenv.conf.example deleted file mode 100644 index e92e25e5..00000000 --- a/apache/apache.virtenv.conf.example +++ /dev/null @@ -1,34 +0,0 @@ - - ServerAdmin tim@linfiniti.com - ServerName qgis-django.localhost - ServerAlias www.qgis-django.localhost - DocumentRoot /var/www - CustomLog /var/log/apache2/qgis-django.access.log combined - # Possible values include: debug, info, notice, warn, error, crit, - # alert, emerg. - LogLevel debug - #warn - ErrorLog /var/log/apache2/qgis-django.error.log - ServerSignature Off - WSGIScriptAlias / /home/web/qgis-django/apache/django.wsgi - WSGIDaemonProcess qgis-django user=timlinux group=timlinux processes=1 threads=10 python-path=/home/web/qgis-django/python/lib/python2.6/site-packages - WSGIProcessGroup qgis-django - - Order deny,allow - Allow from all - - - Alias /media /home/web/qgis-django/python/lib/python2.6/site-packages/django/contrib/admin/media/ - - Order deny,allow - Allow from all - SetHandler None - - - Alias /static /home/web/qgis-django/qgis/static - - Order deny,allow - Allow from all - SetHandler None - - diff --git a/apache/django.wsgi b/apache/django.wsgi deleted file mode 100644 index 9859c572..00000000 --- a/apache/django.wsgi +++ /dev/null @@ -1,15 +0,0 @@ -import os -import sys - -ROOT_PROJECT_FOLDER = os.path.dirname(__file__) -path1 = os.path.abspath(os.path.join(ROOT_PROJECT_FOLDER, "..")) -path2 = os.path.abspath(os.path.join(ROOT_PROJECT_FOLDER, "..", "qgis-app")) - -os.environ["DJANGO_SETTINGS_MODULE"] = "settings" - -import django.core.handlers.wsgi - -application = django.core.handlers.wsgi.WSGIHandler() -sys.path.append(path1) -sys.path.append(path2) -file("/tmp/log.txt", "wt").write(str(sys.path)) diff --git a/backup.sh b/backup.sh deleted file mode 100755 index d794129e..00000000 --- a/backup.sh +++ /dev/null @@ -1,98 +0,0 @@ -#!/bin/bash - -#First the database backups on the remote server -SOURCE=/mnt/HC_Volume_4113275 -DEST=backups -OBJECTIVE=$2 -REMOVE_FILE=$3 - -shopt -s nocasematch -if [ -z "$OBJECTIVE" ] -then - OBJECTIVE='DOWNLOAD' -elif [[ $OBJECTIVE == 'upload' ]]; then - #statements - OBJECTIVE='UPLOAD' - TEMP=$SOURCE - SOURCE=$DEST - DEST=$TEMP -else - OBJECTIVE='DOWNLOAD' -fi -if [[ $REMOVE_FILE == 'True' ]]; then - REMOVE_FILE='-e' -else - REMOVE_FILE='' -fi -shopt -u nocasematch - -echo 'Source :' $SOURCE -echo 'Destination :' $DEST -echo 'Objective :' $OBJECTIVE - -lftp -u kartoza, sftp://plugins.qgis.org -e "mirror $REMOVE_FILE $SOURCE $DEST; bye" - -#Next the plugin backups on the remote server -SOURCE=/mnt/HC_Volume_4113256/packages -DEST=static -OBJECTIVE=$2 -REMOVE_FILE=$3 - -shopt -s nocasematch -if [ -z "$OBJECTIVE" ] -then - OBJECTIVE='DOWNLOAD' -elif [[ $OBJECTIVE == 'upload' ]]; then - #statements - OBJECTIVE='UPLOAD' - TEMP=$SOURCE - SOURCE=$DEST - DEST=$TEMP -else - OBJECTIVE='DOWNLOAD' -fi -if [[ $REMOVE_FILE == 'True' ]]; then - REMOVE_FILE='-e' -else - REMOVE_FILE='' -fi -shopt -u nocasematch - -echo 'Source :' $SOURCE -echo 'Destination :' $DEST -echo 'Objective :' $OBJECTIVE - -lftp -u kartoza, sftp://plugins.qgis.org -e "mirror $REMOVE_FILE $SOURCE $DEST; bye" - - -#Next the style backups on the remote server -SOURCE=/mnt/HC_Volume_4113256/styles -DEST=styles -OBJECTIVE=$2 -REMOVE_FILE=$3 - -shopt -s nocasematch -if [ -z "$OBJECTIVE" ] -then - OBJECTIVE='DOWNLOAD' -elif [[ $OBJECTIVE == 'upload' ]]; then - #statements - OBJECTIVE='UPLOAD' - TEMP=$SOURCE - SOURCE=$DEST - DEST=$TEMP -else - OBJECTIVE='DOWNLOAD' -fi -if [[ $REMOVE_FILE == 'True' ]]; then - REMOVE_FILE='-e' -else - REMOVE_FILE='' -fi -shopt -u nocasematch - -echo 'Source :' $SOURCE -echo 'Destination :' $DEST -echo 'Objective :' $OBJECTIVE - -lftp -u kartoza, sftp://plugins.qgis.org -e "mirror $REMOVE_FILE $SOURCE $DEST; bye" diff --git a/createdb.sh b/createdb.sh deleted file mode 100644 index b1ae4052..00000000 --- a/createdb.sh +++ /dev/null @@ -1,5 +0,0 @@ -#!/bin/bash -createdb qgis-django -createlang plpgsql qgis-django -psql qgis-django < /usr/share/postgresql/8.4/contrib/postgis-1.5/postgis.sql -psql qgis-django < /usr/share/postgresql/8.4/contrib/postgis-1.5/spatial_ref_sys.sql diff --git a/doc/APPS.t2t b/doc/APPS.t2t index 566ad128..0892ed4b 100644 --- a/doc/APPS.t2t +++ b/doc/APPS.t2t @@ -36,7 +36,6 @@ Tim Sutton 2010 = Planned applications= - + plugins - a django app for managing the QGIS python plugin repository + users - a django app for creating a community map and some demographics + snippets - a django app for users to share python and c++ snippets showing how to program QGIS + styles - a django app for users to publish the QGIS styles they have created diff --git a/dockerize/.env.template b/dockerize/.env.template index f7fc8963..fedbd18c 100644 --- a/dockerize/.env.template +++ b/dockerize/.env.template @@ -1,6 +1,3 @@ -# RabbitMQ host -RABBITMQ_HOST=rabbitmq - # Database variables DATABASE_NAME=gis DATABASE_USERNAME=docker @@ -12,9 +9,9 @@ DJANGO_SETTINGS_MODULE=settings_docker DEBUG=False # Docker volumes -QGISPLUGINS_STATIC_VOLUME=static-data -QGISPLUGINS_MEDIA_VOLUME=media-data -QGISPLUGINS_BACKUP_VOLUME=backups-data +QGISHUB_STATIC_VOLUME=static-data +QGISHUB_MEDIA_VOLUME=media-data +QGISHUB_BACKUP_VOLUME=backups-data # Email variables EMAIL_BACKEND='django.core.mail.backends.smtp.EmailBackend' @@ -25,10 +22,10 @@ EMAIL_HOST_USER='' EMAIL_HOST_PASSWORD='' # URL -DEFAULT_PLUGINS_SITE='https://plugins.qgis.org/' +DEFAULT_HUB_SITE='https://hub.qgis.org/' # ENV: debug or prod -QGISPLUGINS_ENV=debug +QGISHUB_ENV=debug # Ldap ENABLE_LDAP=False @@ -41,7 +38,7 @@ SENTRY_DSN='' SENTRY_RATE=1.0 # Download stats URL -METABASE_DOWNLOAD_STATS_URL='https://plugins.qgis.org/metabase/public/dashboard/' +METABASE_DOWNLOAD_STATS_URL='https://hub.qgis.org/metabase/public/dashboard/' # Uwsgi Docker image -UWSGI_DOCKER_IMAGE='qgis/qgis-plugins-uwsgi:latest' \ No newline at end of file +UWSGI_DOCKER_IMAGE='qgis/qgis-hub-uwsgi:latest' \ No newline at end of file diff --git a/dockerize/Makefile b/dockerize/Makefile index 1522cfbc..bb12b0f0 100644 --- a/dockerize/Makefile +++ b/dockerize/Makefile @@ -1,4 +1,4 @@ -PROJECT_ID := qgis-plugins +PROJECT_ID := qgis-hub SHELL := /bin/bash @@ -35,7 +35,7 @@ web: db @echo "------------------------------------------------------------------" @echo "Running in production mode" @echo "------------------------------------------------------------------" - @docker compose -p $(PROJECT_ID) up -d --scale uwsgi=2 web worker beat dbbackups + @docker compose -p $(PROJECT_ID) up -d --scale uwsgi=2 web dbbackups certbot: web @echo @@ -129,12 +129,6 @@ create-test-db: @docker compose -p $(PROJECT_ID) exec db su - postgres -c "psql -c 'create database test_db;'" @docker compose -p $(PROJECT_ID) exec db su - postgres -c "psql -d test_db -c 'create extension postgis;'" -rebuild_index: - @echo - @echo "------------------------------------------------------------------" - @echo "Rebuild search index in PRODUCTION mode" - @echo "------------------------------------------------------------------" - @docker compose -p $(PROJECT_ID) exec uwsgi bash -c 'python manage.py rebuild_index' uwsgi-shell: @echo @@ -222,7 +216,7 @@ devweb: db @echo "------------------------------------------------------------------" @echo "Running in DEVELOPMENT mode" @echo "------------------------------------------------------------------" - @docker compose -p $(PROJECT_ID) up --no-deps -d devweb rabbitmq worker beat + @docker compose -p $(PROJECT_ID) up --no-deps -d devweb devweb-runserver: devweb @echo diff --git a/dockerize/README.md b/dockerize/README.md index aa59cea4..0e73e520 100644 --- a/dockerize/README.md +++ b/dockerize/README.md @@ -1,7 +1,7 @@ # Docker compose commands Documentation ## Overview -This doc is designed for managing a Docker-based project with the ID `qgis-plugins`. It includes various commands for building, running, and maintaining both production and development environments. Below is a detailed description of each command available in the Makefile. +This doc is designed for managing a Docker-based project with the ID `qgis-hub`. It includes various commands for building, running, and maintaining both production and development environments. Below is a detailed description of each command available in the Makefile. ## Commands @@ -164,7 +164,7 @@ make build-dev make devweb-test ``` -- **devweb:** Starts the `devweb` container for development, along with RabbitMQ, worker, and beat containers. +- **devweb:** Starts the `devweb` container for development. ```sh make devweb ``` diff --git a/dockerize/docker-compose.override.template.yml b/dockerize/docker-compose.override.template.yml deleted file mode 100644 index bb0fdd8a..00000000 --- a/dockerize/docker-compose.override.template.yml +++ /dev/null @@ -1,55 +0,0 @@ -version: '3' -services: - devweb: - # Note you cannot scale if you use container_name - image: kartoza/qgis-plugins-uwsgi:dev-latest - container_name: qgis-plugins-devweb - volumes: - - ../qgis-app:/home/web/django_project - - ./static:/home/web/static:rw - - ./media:/home/web/media:rw - build: - context: ${PWD}/../ - dockerfile: dockerize/docker/Dockerfile - target: dev - ports: - # for django test server - - "62202:8080" - # for ssh - - "62203:22" - - beat: - volumes: - - ../qgis-app:/home/web/django_project - - ./static:/home/web/static:rw - - ./media:/home/web/media:rw - - worker: - volumes: - - ../qgis-app:/home/web/django_project - - ./static:/home/web/static:rw - - ./media:/home/web/media:rw - - uwsgi: - container_name: qgis-plugins-uwsgi - volumes: - - ../qgis-app:/home/web/django_project - - ./static:/home/web/static:rw - - ./media:/home/web/media:rw - build: - context: ${PWD}/../ - dockerfile: dockerize/docker/Dockerfile - target: prod - - db: - volumes: - - ./postgres_data:/var/lib/postgresql - - ./backups:/backups - - web: - volumes: - - ./sites-enabled:/etc/nginx/conf.d:ro - - ./static:/home/web/static:ro - - ./media:/home/web/media:ro - ports: - - "62201:8080" diff --git a/dockerize/docker-compose.override.test.yml b/dockerize/docker-compose.override.test.yml deleted file mode 100644 index e10fe9a1..00000000 --- a/dockerize/docker-compose.override.test.yml +++ /dev/null @@ -1,19 +0,0 @@ -version: '3' -services: - devweb: - # Note you cannot scale if you use container_name - image: kartoza/qgis-plugins-uwsgi:dev-latest - container_name: qgis-plugins-devweb - volumes: - - ../qgis-app:/home/web/django_project - - ./static:/home/web/static:rw - - ./media:/home/web/media:rw - build: - context: ${PWD}/../ - dockerfile: dockerize/docker/Dockerfile - target: dev - ports: - # for django test server - - "62202:8080" - # for ssh - - "62203:22" diff --git a/dockerize/docker-compose.yml b/dockerize/docker-compose.yml index d5791fac..4832f7fc 100644 --- a/dockerize/docker-compose.yml +++ b/dockerize/docker-compose.yml @@ -1,14 +1,12 @@ volumes: postgres_data: - rabbitmq: - celerybeat-schedule: static-data: media-data: backups-data: services: db: - container_name: qgis-plugins-db + container_name: qgis-hub-db image: kartoza/postgis:16-3.4 environment: - ALLOW_IP_RANGE=0.0.0.0/0 @@ -21,7 +19,7 @@ services: - DEFAULT_CTYPE=en_GB.utf8 volumes: - postgres_data:/opt/postgres/data - - ${QGISPLUGINS_BACKUP_VOLUME}:/backups + - ${QGISHUB_BACKUP_VOLUME}:/backups restart: unless-stopped networks: internal: @@ -37,12 +35,10 @@ services: - DATABASE_PASSWORD=${DATABASE_PASSWORD:-docker} - DATABASE_HOST=${DATABASE_HOST:-db} - DJANGO_SETTINGS_MODULE=${DJANGO_SETTINGS_MODULE:-settings_docker} - - VIRTUAL_HOST=${VIRTUAL_HOST:-plugins.kartoza.com} + - VIRTUAL_HOST=${VIRTUAL_HOST:-hub.kartoza.com} - VIRTUAL_PORT=${VIRTUAL_PORT:-8080} - DEBUG=${DEBUG:-False} - ENABLE_LDAP=${ENABLE_LDAP:-False} - - RABBITMQ_HOST=${RABBITMQ_HOST:-rabbitmq} - - BROKER_URL=amqp://rabbitmq:5672 - METABASE_DOWNLOAD_STATS_URL=${METABASE_DOWNLOAD_STATS_URL:-/metabase} - EMAIL_BACKEND=${EMAIL_BACKEND} - EMAIL_HOST=${EMAIL_HOST} @@ -50,19 +46,17 @@ services: - EMAIL_USE_TLS=${EMAIL_USE_TLS} - EMAIL_HOST_USER=${EMAIL_HOST_USER:-automation} - EMAIL_HOST_PASSWORD=${EMAIL_HOST_PASSWORD} - - DEFAULT_PLUGINS_SITE=${DEFAULT_PLUGINS_SITE:-https://plugins.qgis.org/} + - DEFAULT_HUB_SITE=${DEFAULT_HUB_SITE:-https://hub.qgis.org/} - SENTRY_DSN=${SENTRY_DSN} - SENTRY_RATE=${SENTRY_RATE} volumes: - ../qgis-app:/home/web/django_project - ./docker/uwsgi.conf:/uwsgi.conf - - ${QGISPLUGINS_STATIC_VOLUME}:/home/web/static:rw - - ${QGISPLUGINS_MEDIA_VOLUME}:/home/web/media:rw - - celerybeat-schedule:/home/web/celerybeat-schedule:rw + - ${QGISHUB_STATIC_VOLUME}:/home/web/static:rw + - ${QGISHUB_MEDIA_VOLUME}:/home/web/media:rw command: uwsgi --ini /uwsgi.conf depends_on: - db - - rabbitmq restart: unless-stopped user: root networks: @@ -73,15 +67,15 @@ services: # from prod environment if wanted devweb: <<: *uwsgi-common - container_name: qgis-plugins-devweb + container_name: qgis-hub-devweb build: context: ${PWD}/../ dockerfile: dockerize/docker/Dockerfile target: dev volumes: - ../qgis-app:/home/web/django_project - - ${QGISPLUGINS_STATIC_VOLUME}:/home/web/static:rw - - ${QGISPLUGINS_MEDIA_VOLUME}:/home/web/media:rw + - ${QGISHUB_STATIC_VOLUME}:/home/web/static:rw + - ${QGISHUB_MEDIA_VOLUME}:/home/web/media:rw ports: # for django test server - "62202:8081" @@ -90,49 +84,9 @@ services: networks: internal: - rabbitmq: - image: rabbitmq:3.7-alpine - hostname: rabbitmq - volumes: - - rabbitmq:/var/lib/rabbitmq - restart: unless-stopped - networks: - internal: - healthcheck: - test: ["CMD", "rabbitmqctl", "status"] - interval: 10s - timeout: 5s - retries: 5 - - beat: - <<: *uwsgi-common - container_name: qgis-plugins-beat - depends_on: - rabbitmq: - condition: service_healthy - working_dir: /home/web/django_project - entrypoint: [ ] - command: celery --app=plugins.celery:app beat -s /home/web/celerybeat-schedule/schedule -l INFO - networks: - internal: - - worker: - <<: *uwsgi-common - container_name: qgis-plugins-worker - depends_on: - db: - condition: service_started - beat: - condition: service_started - working_dir: /home/web/django_project - entrypoint: [] - command: celery -A plugins worker -l INFO - networks: - internal: - web: # Note you cannot scale if you use container_name - container_name: qgis-plugins-web + container_name: qgis-hub-web image: nginx hostname: web entrypoint: @@ -142,8 +96,8 @@ services: - "443:443" volumes: - ./sites-enabled:/etc/nginx/sites-available/:ro - - ${QGISPLUGINS_STATIC_VOLUME}:/home/web/static:ro - - ${QGISPLUGINS_MEDIA_VOLUME}:/home/web/media:ro + - ${QGISHUB_STATIC_VOLUME}:/home/web/static:ro + - ${QGISHUB_MEDIA_VOLUME}:/home/web/media:ro - ./webroot:/var/www/webroot - ./certbot-etc:/etc/letsencrypt depends_on: @@ -156,7 +110,7 @@ services: max-file: "10" restart: unless-stopped command: - - ${QGISPLUGINS_ENV} + - ${QGISHUB_ENV} networks: internal: @@ -164,13 +118,13 @@ services: image: kartoza/pg-backup:16-3.4 hostname: pg-backups volumes: - - ${QGISPLUGINS_BACKUP_VOLUME}:/backups + - ${QGISHUB_BACKUP_VOLUME}:/backups depends_on: - db environment: # take care to let the project name below match that # declared in the top of the makefile - - DUMPPREFIX=${DUMPPREFIX:-QGIS_PLUGINS} + - DUMPPREFIX=${DUMPPREFIX:-QGIS_HUB} - POSTGRES_USER=${DATABASE_USERNAME:-docker} - POSTGRES_PASS=${DATABASE_PASSWORD:-docker} - POSTGRES_PORT=${POSTGRES_PORT:-5432} @@ -200,7 +154,7 @@ services: - ./certbot-etc:/etc/letsencrypt depends_on: - web - command: certonly --webroot --webroot-path=/var/www/webroot --email admin@qgis.org --agree-tos --no-eff-email --force-renewal -d plugins.qgis.org + command: certonly --webroot --webroot-path=/var/www/webroot --email admin@qgis.org --agree-tos --no-eff-email --force-renewal -d hub.qgis.org networks: internal: diff --git a/dockerize/docker/REQUIREMENTS.txt b/dockerize/docker/REQUIREMENTS.txt index e3827683..84479f0d 100644 --- a/dockerize/docker/REQUIREMENTS.txt +++ b/dockerize/docker/REQUIREMENTS.txt @@ -26,20 +26,6 @@ django-debug-toolbar~=4.2 whoosh~=2.7 django-haystack~=3.2 -# Feedjack==0.9.18 -# So use George's fork rather -# git+https://github.com/Erve1879/feedjack.git -# George's is also broken: use my fork (django 1.8 ready) -# git+https://github.com/elpaso/feedjack.git -# His is also broken, use dimasciput (django 2.2 ready) -# git+https://github.com/dimasciput/feedjack.git -# For django 4, use Xpirix (django 4.2 ready) -git+https://github.com/Xpirix/feedjack.git -feedparser~=6.0 -celery~=5.3 - -# pin due to issues with a breaking change -# https://github.com/celery/celery/issues/7783 importlib_metadata<5 requests~=2.31 diff --git a/dockerize/plugins_mirror.sh b/dockerize/plugins_mirror.sh deleted file mode 100644 index 0da24026..00000000 --- a/dockerize/plugins_mirror.sh +++ /dev/null @@ -1,66 +0,0 @@ -#!/bin/bash - -#First the database backups on the remote server -SOURCE=/mnt/HC_Volume_4113275 -DEST=backups -OBJECTIVE=$2 -REMOVE_FILE=$3 - -shopt -s nocasematch -if [ -z "$OBJECTIVE" ] -then - OBJECTIVE='DOWNLOAD' -elif [[ $OBJECTIVE == 'upload' ]]; then - #statements - OBJECTIVE='UPLOAD' - TEMP=$SOURCE - SOURCE=$DEST - DEST=$TEMP -else - OBJECTIVE='DOWNLOAD' -fi -if [[ $REMOVE_FILE == 'True' ]]; then - REMOVE_FILE='-e' -else - REMOVE_FILE='' -fi -shopt -u nocasematch - -echo 'Source :' $SOURCE -echo 'Destination :' $DEST -echo 'Objective :' $OBJECTIVE - -lftp -u kartoza, sftp://plugins.qgis.org -e "mirror $REMOVE_FILE $SOURCE $DEST; bye" - - -#Next the plugin backups on the remote server -SOURCE=/mnt/HC_Volume_4113256/packages -DEST=static -OBJECTIVE=$2 -REMOVE_FILE=$3 - -shopt -s nocasematch -if [ -z "$OBJECTIVE" ] -then - OBJECTIVE='DOWNLOAD' -elif [[ $OBJECTIVE == 'upload' ]]; then - #statements - OBJECTIVE='UPLOAD' - TEMP=$SOURCE - SOURCE=$DEST - DEST=$TEMP -else - OBJECTIVE='DOWNLOAD' -fi -if [[ $REMOVE_FILE == 'True' ]]; then - REMOVE_FILE='-e' -else - REMOVE_FILE='' -fi -shopt -u nocasematch - -echo 'Source :' $SOURCE -echo 'Destination :' $DEST -echo 'Objective :' $OBJECTIVE - -lftp -u kartoza, sftp://plugins.qgis.org -e "mirror $REMOVE_FILE $SOURCE $DEST; bye": diff --git a/dockerize/production/Dockerfile b/dockerize/production/Dockerfile deleted file mode 100644 index 3631f46a..00000000 --- a/dockerize/production/Dockerfile +++ /dev/null @@ -1,43 +0,0 @@ -#--------- Generic stuff all our Dockerfiles should start with so we get caching ------------ -# Note this base image is based on debian -FROM kartoza/django-base:3.7 -MAINTAINER Dimas Ciputra - -#RUN ln -s /bin/true /sbin/initctl -RUN apt-get clean all - -# Debian stretch/updates release issue. please see https://serverfault.com/a/1130167 -RUN echo "deb http://archive.debian.org/debian stretch main contrib non-free" > /etc/apt/sources.list - -RUN apt-get update && apt-get install -y libsasl2-dev python-dev libldap2-dev libssl-dev - -ARG BRANCH_TAG=develop -RUN mkdir -p /usr/src; mkdir -p /home/web && \ - git clone --depth=1 git://github.com/qgis/QGIS-Django.git --branch ${BRANCH_TAG} /usr/src/plugins/ && \ - rm -rf /home/web/django_project && \ - ln -s /usr/src/plugins/qgis-app /home/web/django_project - -# Install C library for geoip2 -RUN apt-get install -y libmaxminddb0 libmaxminddb-dev mmdb-bin - -RUN cd /usr/src/plugins/dockerize/docker && \ - pip install --upgrade pip && \ - pip install -r REQUIREMENTS.txt && \ - pip install uwsgi && \ - rm -rf /uwsgi.conf && \ - ln -s ${PWD}/uwsgi.conf /uwsgi.conf - -# GeoIp mmdb -RUN apt-get update && apt-get install -y curl && curl -LJO https://github.com/P3TERX/GeoLite.mmdb/raw/download/GeoLite2-City.mmdb && \ - mkdir /var/opt/maxmind && \ - mv GeoLite2-City.mmdb /var/opt/maxmind/GeoLite2-City.mmdb - -ENV GEOIP_PATH=/var/opt/maxmind/ - -# Open port 8080 as we will be running our uwsgi socket on that -EXPOSE 8080 - -#USER www-data - -WORKDIR /home/web/django_project -CMD ["uwsgi", "--ini", "/uwsgi.conf"] diff --git a/dockerize/production/build.sh b/dockerize/production/build.sh deleted file mode 100755 index 62f15fc0..00000000 --- a/dockerize/production/build.sh +++ /dev/null @@ -1,31 +0,0 @@ -#!/usr/bin/env bash -if [ -z "$REPO_NAME" ]; then - REPO_NAME=kartoza -fi - -if [ -z "$IMAGE_NAME" ]; then - IMAGE_NAME=qgis-plugins-uwsgi -fi - -if [ -z "$TAG_NAME" ]; then - TAG_NAME=latest -fi - -if [ -z "$BUILD_ARGS" ]; then - BUILD_ARGS="--pull --no-cache" -fi - -# Build Args Environment - -if [ -z "$BRANCH_TAG" ]; then - BRANCH_TAG=dockerize -fi - -echo "BRANCH_TAG: ${BRANCH_TAG}" -echo "Build: $REPO_NAME/$IMAGE_NAME:$TAG_NAME" - -docker build -t ${REPO_NAME}/${IMAGE_NAME} \ - --build-arg BRANCH_TAG=${BRANCH_TAG} \ - ${BUILD_ARGS} . -docker tag ${REPO_NAME}/${IMAGE_NAME}:latest ${REPO_NAME}/${IMAGE_NAME}:${TAG_NAME} -docker push ${REPO_NAME}/${IMAGE_NAME}:${TAG_NAME} diff --git a/dockerize/production/rancher/docker-compose.yml b/dockerize/production/rancher/docker-compose.yml deleted file mode 100644 index 33ca631a..00000000 --- a/dockerize/production/rancher/docker-compose.yml +++ /dev/null @@ -1,100 +0,0 @@ -version: '2' -volumes: - nginx-conf-data: {} - postgis-history-data: {} - postgis-data: {} - django-statics-data: {} - django-media-data: {} -services: - web: - image: nginx - entrypoint: - - /etc/nginx/sites-available/docker-entrypoint.sh - volumes: - - nginx-conf-data:/etc/nginx/sites-available - - django-statics-data:/home/web/static:ro - - django-media-data:/home/web/media:ro - links: - - uwsgi:uwsgi - command: - - prod - nginx-conf: - image: kartoza/ford3_nginx_conf:v1.0.3-20190728 - environment: - TARGET: /etc/nginx/sites-available - volumes: - - nginx-conf-data:/etc/nginx/sites-available - labels: - io.rancher.container.pull_image: always - io.rancher.container.start_once: 'true' - SFTP: - image: atmoz/sftp - stdin_open: true - working_dir: / - volumes: - - postgis-history-data:/home/kartoza/ftp/postgis-history/ - - django-statics-data:/home/web/static - - django-media-data:/home/web/media - tty: true - ports: - - 2222:22/tcp - command: - - kartoza:kartoza_admin:1001 - labels: - io.rancher.container.pull_image: always - uwsgi: - image: dimasciput/qgis-plugins-uwsgi:latest - environment: - ADMIN_EMAIL: dimas@kartoza.com - DATABASE_HOST: db - DATABASE_NAME: gis - DATABASE_PASSWORD: docker - DATABASE_USERNAME: docker - DEBUG: 'False' - DEFAULT_FROM_EMAIL: noreply@kartoza.com - DJANGO_ALLOWED_HOSTS: '' - DJANGO_LOG_LEVEL: INFO - DJANGO_SETTINGS_MODULE: settings_docker - PYTHONPATH: /home/web/django_project - SITEURL: www.kartoza.com - working_dir: /home/web/django_project - volumes: - - django-statics-data:/home/web/static:rw - - django-media-data:/home/web/media:rw - - django-reports:/home/web/reports - - django-logs:/var/log/ - links: - - db:db - labels: - io.rancher.container.start_once: 'true' - db: - image: kartoza/postgis:9.6-2.4 - environment: - ALLOW_IP_RANGE: 0.0.0.0/0 - PGDBNAME: gis - PGHOST: localhost - PGPASSWORD: docker - PGUSER: docker - POSTGRES_DBNAME: gis - POSTGRES_PASSWORD: docker - POSTGRES_USER: docker - USERNAME: docker - PASS: docker - volumes: - - postgis-data:/var/lib/postgresql - - postgis-history-data:/backups - dbbackups: - image: kartoza/pg-backup:9.6 - environment: - DUMPPREFIX: PG_QGIS_PLUGINS - PGDATABASE: gis - PGHOST: db - PGPASSWORD: docker - PGPORT: '5432' - PGUSER: docker - volumes: - - postgis-history-data:/backups - links: - - db:db - command: - - /start.sh diff --git a/dockerize/scripts/renew-ssl.sh b/dockerize/scripts/renew-ssl.sh index 7004ead6..d076201a 100644 --- a/dockerize/scripts/renew-ssl.sh +++ b/dockerize/scripts/renew-ssl.sh @@ -14,4 +14,4 @@ #25 11 * * * /bin/bash /home/web/QGIS-Django/dockerize/scripts/renew_ssl.sh > /tmp/ssl-renewal-logs.txt -docker compose -f /home/web/QGIS-Django/dockerize/docker-compose.yml -p qgis-plugins run certbot renew \ No newline at end of file +docker compose -f /home/web/QGIS-Django/dockerize/docker-compose.yml -p qgis-hub run certbot renew \ No newline at end of file diff --git a/dockerize/sites-enabled/prod-ssl.conf b/dockerize/sites-enabled/prod-ssl.conf index 94ca5328..7f461842 100644 --- a/dockerize/sites-enabled/prod-ssl.conf +++ b/dockerize/sites-enabled/prod-ssl.conf @@ -24,7 +24,7 @@ server { # the port your site will be served on listen 80; # the domain name it will serve for - server_name plugins.qgis.org; + server_name hub.qgis.org; # Redirect all HTTP traffic to HTTPS return 301 https://$server_name$request_uri; @@ -48,13 +48,6 @@ server { alias /home/web/static; expires 21d; # cache for 21 days } - location /plugins/plugins.xml { - if ($request_uri !~ "&package_name(.*)") { - rewrite ^/plugins/plugins.xml /web/media/cached_xmls/plugins_$arg_qgis.xml break; - root /home; - expires 600s; - } - } location /archive { # Changed from http_host to host because of error messages when # bots hit urls like this: @@ -116,12 +109,12 @@ server { # SSL Cert listen 443 ssl http2; listen [::]:443 ssl http2; - server_name plugins.qgis.org; + server_name hub.qgis.org; server_tokens off; - ssl_certificate /etc/letsencrypt/live/plugins.qgis.org/fullchain.pem; - ssl_certificate_key /etc/letsencrypt/live/plugins.qgis.org/privkey.pem; + ssl_certificate /etc/letsencrypt/live/hub.qgis.org/fullchain.pem; + ssl_certificate_key /etc/letsencrypt/live/hub.qgis.org/privkey.pem; ssl_buffer_size 8k; @@ -171,13 +164,6 @@ server { alias /home/web/static; expires 21d; # cache for 21 days } - location /plugins/plugins.xml { - if ($request_uri !~ "&package_name(.*)") { - rewrite ^/plugins/plugins.xml /web/media/cached_xmls/plugins_$arg_qgis.xml break; - root /home; - expires 600s; - } - } location /archive { # Changed from http_host to host because of error messages when # bots hit urls like this: diff --git a/dockerize/sites-enabled/prod.conf b/dockerize/sites-enabled/prod.conf index 1aa73956..3ecb165e 100644 --- a/dockerize/sites-enabled/prod.conf +++ b/dockerize/sites-enabled/prod.conf @@ -45,13 +45,6 @@ server { alias /home/web/static; expires 21d; # cache for 21 days } - location /plugins/plugins.xml { - if ($request_uri !~ "&package_name(.*)") { - rewrite ^/plugins/plugins.xml /web/media/cached_xmls/plugins_$arg_qgis.xml break; - root /home; - expires 600s; - } - } location /archive { # Changed from http_host to host because of error messages when # bots hit urls like this: diff --git a/img/Docker_Services.png b/img/Docker_Services.png index 32a0c5e8..451fa929 100644 Binary files a/img/Docker_Services.png and b/img/Docker_Services.png differ diff --git a/img/Hub_Website.png b/img/Hub_Website.png new file mode 100644 index 00000000..692f9913 Binary files /dev/null and b/img/Hub_Website.png differ diff --git a/qgis-app/Makefile b/qgis-app/Makefile deleted file mode 100644 index 260cc2b6..00000000 --- a/qgis-app/Makefile +++ /dev/null @@ -1,2 +0,0 @@ -check: - python manage.py test plugins diff --git a/qgis-app/REQUIREMENTS_plugins.txt b/qgis-app/REQUIREMENTS_plugins.txt deleted file mode 100644 index be3c802d..00000000 --- a/qgis-app/REQUIREMENTS_plugins.txt +++ /dev/null @@ -1,64 +0,0 @@ -django~=4.2 -django-auth-ldap~=4.6 -python-ldap~=3.4 -django-taggit~=5.0 -django-tinymce==4.1.0 -psycopg2-binary~=2.9 -# Updates for Django 2 -git+https://github.com/metamatik/django-templatetag-sugar.git -# Updates for Django 4 -git+https://github.com/Xpirix/django-ratings.git@modernize -django-taggit-autosuggest~=0.4 -django-annoying~=0.10 - -# Updates for Django 2 -# git+https://github.com/elpaso/rpc4django.git@modernize -rpc4django~=0.6 -Pillow~=10.1 -django-taggit-templatetags -# Updates for Django 4 -git+https://github.com/Xpirix/django-simplemenu.git@modernize -django-bootstrap-pagination-forked~=1.7 -django-sortable-listview~=0.43 -sorl-thumbnail~=12.10 -django-extensions~=3.2 -django-debug-toolbar~=4.2 -whoosh~=2.7 -django-haystack~=3.2 - -# Feedjack==0.9.18 -# So use George's fork rather -# git+https://github.com/Erve1879/feedjack.git -# George's is also broken: use my fork (django 1.8 ready) -# git+https://github.com/elpaso/feedjack.git -# His is also broken, use dimasciput (django 2.2 ready) -# git+https://github.com/dimasciput/feedjack.git -# For django 4, use Xpirix (django 4.2 ready) -git+https://github.com/Xpirix/feedjack.git -feedparser~=6.0 -celery~=5.3 - -# pin due to issues with a breaking change -# https://github.com/celery/celery/issues/7783 -importlib_metadata<5 - -requests~=2.31 - -markdown~=3.5 - -djangorestframework~=3.14 -pyjwt~=2.8 -djangorestframework-simplejwt~=5.3 -sorl-thumbnail-serializer-field==0.2.1 -django-rest-auth==0.9.5 -drf-yasg~=1.21 -django-rest-multiple-models==2.1.3 - -django-preferences==1.0.0 -PyWavefront==1.3.3 -geoip2==4.5.0 -django-matomo==0.1.6 -uwsgi~=2.0 -freezegun~=1.4 - -sentry-sdk~=2.2 \ No newline at end of file diff --git a/qgis-app/plugins/middleware.py b/qgis-app/base/middleware.py similarity index 100% rename from qgis-app/plugins/middleware.py rename to qgis-app/base/middleware.py diff --git a/qgis-app/base/models/__init__.py b/qgis-app/base/models/__init__.py index 54125ba0..8b137891 100644 --- a/qgis-app/base/models/__init__.py +++ b/qgis-app/base/models/__init__.py @@ -1 +1 @@ -from .site_preferences import * + diff --git a/qgis-app/base/models/site_preferences.py b/qgis-app/base/models/site_preferences.py deleted file mode 100644 index cc8ab6a7..00000000 --- a/qgis-app/base/models/site_preferences.py +++ /dev/null @@ -1,12 +0,0 @@ -from django.db import models -from preferences.models import Preferences - - -class SitePreference(Preferences): - __module__ = "preferences.models" - qgis_versions = models.TextField( - default="", - blank=True, - help_text="QGIS versions that will be used to " - "generate the plugins_xml, separated by comma.", - ) diff --git a/qgis-app/fixtures/plugins.json b/qgis-app/fixtures/plugins.json deleted file mode 100644 index 3bc7955b..00000000 --- a/qgis-app/fixtures/plugins.json +++ /dev/null @@ -1,90 +0,0 @@ -[ - { - "pk": 1, - "model": "plugins.plugin", - "fields": { - "owners": [ - 2, - 1 - ], - "description": "A Plugin for making coffee", - "name": "Coffee Plugin", - "package_name": "CoffeePlugin", - "downloads": 2, - "featured": false, - "created_by": 1, - "homepage" : "", - "icon": "", - "created_on": "2010-11-24 03:25:32", - "modified_on": "2010-11-25 07:37:05" - } - }, - { - "pk": 2, - "model": "plugins.plugin", - "fields": { - "owners": [ - 1, - 3 - ], - "description": "Plugin to make pizza", - "name": "Pizza Plugin", - "package_name": "PizzaPlugin", - "downloads": 0, - "featured": true, - "homepage" : "", - "icon": "", - "created_by": 1, - "created_on": "2010-11-24 05:24:03", - "modified_on": "2010-11-25 07:36:57" - } - }, - { - "pk": 3, - "model": "plugins.pluginversion", - "fields": { - "plugin": 1, - "downloads": 0, - "changelog": "Added coffee filtering", - "created_by": 1, - "package": "packages/test.txt", - "min_qg_version": "1", - "created_on": "2010-11-24 04:26:34", - "version": "1.3", - "approved": true, - "experimental": false - } - }, - { - "pk": 1, - "model": "plugins.pluginversion", - "fields": { - "plugin": 1, - "downloads": 1, - "changelog": "Added coffee filtering", - "created_by": 1, - "package": "packages/tmp.txt", - "min_qg_version": "1", - "created_on": "2010-11-24 03:26:27", - "version": "1.2", - "approved": false, - "experimental": false - } - }, - { - "pk": 2, - "model": "plugins.pluginversion", - "fields": { - "plugin": 1, - "downloads": 1, - "changelog": "Added coffee filtering", - "created_by": 1, - "package": "packages/tmp_1.txt", - "min_qg_version": "1", - "created_on": "2010-11-24 03:27:23", - "version": "1.4", - "approved": true, - "experimental": true - } - } -] diff --git a/qgis-app/homepage.py b/qgis-app/homepage.py deleted file mode 100644 index a7e9e51f..00000000 --- a/qgis-app/homepage.py +++ /dev/null @@ -1,31 +0,0 @@ -from django.contrib.flatpages.models import FlatPage -from django.shortcuts import render -from django.template import RequestContext -from django.utils.translation import gettext_lazy as _ -from plugins.models import Plugin - -# from feedjack.models import Post - - -def homepage(request): - """ - Renders the home page - """ - latest = Plugin.latest_objects.all()[:10] - featured = Plugin.featured_objects.all()[:10] - popular = Plugin.popular_objects.all()[:10] - try: - content = FlatPage.objects.get(url="/").content - except FlatPage.DoesNotExist: - content = _('To add content here, create a FlatPage with url="/"') - - return render( - request, - "flatpages/homepage.html", - { - "featured": featured, - "latest": latest, - "popular": popular, - "content": content, - }, - ) diff --git a/qgis-app/plugins/__init__.py b/qgis-app/plugins/__init__.py deleted file mode 100644 index cf9dc02f..00000000 --- a/qgis-app/plugins/__init__.py +++ /dev/null @@ -1,17 +0,0 @@ -from __future__ import absolute_import, unicode_literals - -# This will make sure the app is always imported when -# Django starts so that shared_task will use this app. -from .celery import app as celery_app - -default_app_config = "plugins.apps.PluginsConfig" -__all__ = [ - "celery_app", - "models", - "views", - "admin", - "urls", - "api", - "forms", - "validator", -] diff --git a/qgis-app/plugins/admin.py b/qgis-app/plugins/admin.py deleted file mode 100644 index 81b5085d..00000000 --- a/qgis-app/plugins/admin.py +++ /dev/null @@ -1,48 +0,0 @@ -from django.contrib import admin -from plugins.models import Plugin, PluginVersion, PluginVersionDownload # , PluginCrashReport - - -class PluginAdmin(admin.ModelAdmin): - list_filter = ("featured",) - list_display = ( - "name", - "featured", - "created_by", - "created_on", - "downloads", - "stable", - "experimental", - ) - search_fields = ("name",) - - -class PluginVersionAdmin(admin.ModelAdmin): - list_filter = ("experimental", "approved", "plugin") - list_display = ( - "plugin", - "approved", - "version", - "experimental", - "created_on", - "downloads", - ) - - -class PluginVersionDownloadAdmin(admin.ModelAdmin): - list_display = ( - "plugin_version", - "download_date", - "download_count" - ) - raw_id_fields = ( - "plugin_version", - ) - -# class PluginCrashReportAdmin(admin.ModelAdmin): -# pass - - -admin.site.register(Plugin, PluginAdmin) -admin.site.register(PluginVersion, PluginVersionAdmin) -admin.site.register(PluginVersionDownload, PluginVersionDownloadAdmin) -# admin.site.register(PluginCrashReport, PluginCrashReportAdmin) diff --git a/qgis-app/plugins/api.py b/qgis-app/plugins/api.py deleted file mode 100644 index 72615d68..00000000 --- a/qgis-app/plugins/api.py +++ /dev/null @@ -1,210 +0,0 @@ -""" -XML-RPC webservices for the plugin web application -""" - -from base64 import b64decode -from io import BytesIO -from xmlrpc.server import Fault - -from django.contrib.contenttypes.models import ContentType -from django.core.exceptions import PermissionDenied, ValidationError -from django.core.files.uploadedfile import InMemoryUploadedFile - -# Transaction -from django.db import IntegrityError, connection -from django.utils.translation import gettext_lazy as _ -from plugins.models import * -from plugins.validator import validator -from plugins.views import plugin_notify -from rpc4django import rpcmethod -from taggit.models import Tag - - -@rpcmethod(name="plugin.maintainers", signature=["string"], login_required=True) -def plugin_maintaners(**kwargs): - """ - Returns a CSV list of plugin maintainers - """ - request = kwargs.get("request") - if not request.user.is_superuser: - raise PermissionDenied() - return "\n".join( - [ - u.email - for u in User.objects.filter( - plugins_created_by__isnull=False, email__isnull=False - ) - .exclude(email="") - .order_by("email") - .distinct() - ] - ) - - -@rpcmethod(name="plugin.upload", signature=["array", "base64"], login_required=True) -def plugin_upload(package, **kwargs): - """ - Creates a new plugin or updates an existing one - Returns an array containing the ID (primary key) of the plugin and the ID of the version. - """ - try: - # JSON-RPC cannot deserialize base64 strings to bytes, do it here instead - if isinstance(package, str): - package = b64decode(package.encode('utf-8')) - - request = kwargs.get("request") - package = BytesIO(package) - package.len = package.getbuffer().nbytes - try: - cleaned_data = dict(validator(package)) - except ValidationError as e: - msg = _( - "File upload must be a valid QGIS Python plugin compressed archive." - ) - raise Fault(1, "%s %s" % (msg, ",".join(e.messages))) - - plugin_data = { - "name": cleaned_data["name"], - "package_name": cleaned_data["package_name"], - "description": cleaned_data["description"], - "created_by": request.user, - "icon": cleaned_data["icon_file"], - "author": cleaned_data["author"], - "email": cleaned_data["email"], - "about": cleaned_data["about"], - } - - # Gets existing plugin - try: - plugin = Plugin.objects.get(package_name=plugin_data["package_name"]) - # Apply new values - plugin.name = plugin_data["name"] - plugin.description = plugin_data["description"] - plugin.icon = plugin_data["icon"] - is_new = False - except Plugin.DoesNotExist: - plugin = Plugin(**plugin_data) - is_new = True - - # Optional Metadata: - if cleaned_data.get("homepage"): - plugin.homepage = cleaned_data.get("homepage") - if cleaned_data.get("tracker"): - plugin.tracker = cleaned_data.get("tracker") - if cleaned_data.get("repository"): - plugin.repository = cleaned_data.get("repository") - if cleaned_data.get("deprecated"): - plugin.deprecated = cleaned_data.get("deprecated") - - plugin.save() - - if is_new: - plugin_notify(plugin) - - # Takes care of tags - if cleaned_data.get("tags"): - plugin.tags.set( - [t.strip().lower() for t in cleaned_data.get("tags").split(",")] - ) - - version_data = { - "plugin": plugin, - "min_qg_version": cleaned_data["qgisMinimumVersion"], - "version": cleaned_data["version"], - "created_by": request.user, - "package": InMemoryUploadedFile( - package, - "package", - "%s.zip" % plugin.package_name, - "application/zip", - package.len, - "UTF-8", - ), - "approved": request.user.has_perm("plugins.can_approve") or plugin.approved, - } - - # Optional version metadata - if cleaned_data.get("experimental"): - version_data["experimental"] = cleaned_data.get("experimental") - if cleaned_data.get("changelog"): - version_data["changelog"] = cleaned_data.get("changelog") - if cleaned_data.get("qgisMaximumVersion"): - version_data["max_qg_version"] = cleaned_data.get("qgisMaximumVersion") - - new_version = PluginVersion(**version_data) - new_version.clean() - new_version.save() - except IntegrityError as e: - # Avoids error: current transaction is aborted, commands ignored until - # end of transaction block - connection.close() - raise Fault(1, e.message) - except ValidationError as e: - raise Fault(1, e.message) - except Exception as e: - raise Fault(1, "%s" % e) - - return (plugin.pk, new_version.pk) - - -@rpcmethod(name="plugin.tags", signature=["array"], login_required=False) -def plugin_tags(**kwargs): - """ - Returns a list of current tags, in alphabetical order - """ - return [t.name for t in Tag.objects.all().order_by("name")] - - -@rpcmethod( - name="plugin.vote", signature=["array", "integer", "integer"], login_required=False -) -def plugin_vote(plugin_id, vote, **kwargs): - """ - Vote a plugin, valid values are 1-5 - """ - try: - request = kwargs.get("request") - except: - msg = _("Invalid request.") - raise ValidationError(msg) - try: - plugin = Plugin.objects.get(pk=plugin_id) - except Plugin.DoesNotExist: - msg = _("Plugin with id %s does not exists.") % plugin_id - raise ValidationError(msg) - if not int(vote) in range(1, 6): - msg = _("%s is not a valid vote (1-5).") % vote - raise ValidationError(msg) - cookies = request.COOKIES - if request.user.is_anonymous: - # Get the cookie - cookie_name = "vote-%s.%s.%s" % ( - ContentType.objects.get(app_label="plugins", model="plugin").pk, - plugin_id, - plugin.rating.field.key[:6], - ) - if not request.COOKIES.get(cookie_name, False): - # Get the IP - ip_address = request.META["REMOTE_ADDR"] - # Check if a recent vote exists - rating = ( - plugin.rating.get_ratings() - .filter( - cookie__isnull=False, - ip_address=ip_address, - date_changed__gte=datetime.datetime.now() - - datetime.timedelta(days=10), - ) - .order_by("-date_changed") - ) - # Change vote if exists - if len(rating): - cookies = {cookie_name: rating[0].cookie} - return [ - plugin.rating.add( - score=int(vote), - user=request.user, - ip_address=request.META["REMOTE_ADDR"], - cookies=cookies, - ) - ] diff --git a/qgis-app/plugins/apps.py b/qgis-app/plugins/apps.py deleted file mode 100644 index f11ac0de..00000000 --- a/qgis-app/plugins/apps.py +++ /dev/null @@ -1,9 +0,0 @@ -from django.apps import AppConfig - - -class PluginsConfig(AppConfig): - name = "plugins" - verbose_name = "QGIS Plugins" - - def ready(self): - from . import api diff --git a/qgis-app/plugins/celery.py b/qgis-app/plugins/celery.py deleted file mode 100644 index cdc9b450..00000000 --- a/qgis-app/plugins/celery.py +++ /dev/null @@ -1,28 +0,0 @@ -from __future__ import absolute_import - -import os - -from celery import Celery -import logging - -logger = logging.getLogger('plugins') - - -# set the default Django settings module for the 'celery' program. -os.environ.setdefault("DJANGO_SETTINGS_MODULE", "settings_docker") - -app = Celery("plugins") - -# Using a string here means the worker doesn't have to serialize -# the configuration object to child processes. -# - namespace='CELERY' means all celery-related configuration keys -# should have a `CELERY_` prefix. -app.config_from_object("django.conf:settings", namespace="CELERY") - -# Load task modules from all registered Django app configs. -app.autodiscover_tasks() - - -@app.task(bind=True) -def debug_task(self): - print("Request: {0!r}".format(self.request)) diff --git a/qgis-app/plugins/decorators.py b/qgis-app/plugins/decorators.py deleted file mode 100644 index 69509b70..00000000 --- a/qgis-app/plugins/decorators.py +++ /dev/null @@ -1,39 +0,0 @@ -from functools import wraps -from django.http import HttpResponseForbidden -from rest_framework_simplejwt.authentication import JWTAuthentication -from plugins.models import Plugin, PluginOutstandingToken -from rest_framework_simplejwt.exceptions import InvalidToken, TokenError -from rest_framework_simplejwt.token_blacklist.models import BlacklistedToken, OutstandingToken -import datetime - -def has_valid_token(function): - @wraps(function) - def wrap(request, *args, **kwargs): - auth_token = request.META.get("HTTP_AUTHORIZATION") - package_name = kwargs.get('package_name') - if not str(auth_token).startswith('Bearer'): - raise InvalidToken("Invalid token") - - # Validate JWT token - authentication = JWTAuthentication() - try: - validated_token = authentication.get_validated_token(auth_token[7:]) - plugin_id = validated_token.payload.get('plugin_id') - jti = validated_token.payload.get('refresh_jti') - token_id = OutstandingToken.objects.get(jti=jti).pk - is_blacklisted = BlacklistedToken.objects.filter(token_id=token_id).exists() - if not plugin_id or is_blacklisted: - raise InvalidToken("Invalid token") - - plugin = Plugin.objects.get(pk=plugin_id) - if not plugin or plugin.package_name != package_name: - raise InvalidToken("Invalid token") - plugin_token = PluginOutstandingToken.objects.get(token__pk=token_id, plugin=plugin) - plugin_token.last_used_on = datetime.datetime.now() - plugin_token.save() - request.plugin_token = plugin_token - return function(request, *args, **kwargs) - except (InvalidToken, TokenError) as e: - return HttpResponseForbidden(str(e)) - - return wrap diff --git a/qgis-app/plugins/docs/future_developments.rst b/qgis-app/plugins/docs/future_developments.rst deleted file mode 100644 index 1123ebd8..00000000 --- a/qgis-app/plugins/docs/future_developments.rst +++ /dev/null @@ -1,7 +0,0 @@ -Future developments -=================== - -* add live search support to plugin tags -* add full text search to all apps (django-haystack is already in place) -* better error messages with links to conflicting plugins -* add &locale= to plugins.xml and support localization of plugins web interface diff --git a/qgis-app/plugins/docs/introduction.rst b/qgis-app/plugins/docs/introduction.rst deleted file mode 100644 index 8f041562..00000000 --- a/qgis-app/plugins/docs/introduction.rst +++ /dev/null @@ -1,109 +0,0 @@ -======================== -QGIS Plugins application -======================== - -Author: Alessandro Pasotti (www.itopen.it) - -The Plugin model -================ - -The plugin model represents a QGIS plugin and holds general informations such as title and description and icon. - -The plugin can have zero or more *owners* (also named 'collaborators'), *owners* have the same permissions of the original plugin creator. - -Permissions ------------ - -These rules have been implemented: - -* every registered user can add a new plugin -* *staff* users can approve or disapprove all plugin versions -* users which have the special permission `plugins.can_approve` get the versions they upload automatically approved -* users which have the special permission `plugins.can_approve` can approve versions uploaded by others as long as they are in the list of the plugin *owners* -* a particular plugin can be deleted and edited only by *staff* users and plugin *owners* -* if a user without `plugins.can_approve` permission uploads a new version, the plugin version is automatically unapproved. - - -Trust management ----------------- - -Staff members can grant *trust* to selected plugin creators setting `plugins.can_approve` permission through the front-end application. - -The plugin details view offers direct links to grant trust to the plugin creator or the plugin *owners*. - - -The PluginVersion model -======================= - -Each plugin can have several versions, a version specify the minimum QGIS version needed in order to run that particular plugin version and other informations such as if the version belongs to the "stable" o to the "experimental" branch. - -Validation ----------- - -The validation takes place in the PluginVersions forms, at loading time, the compressed file is checked for: - -* file size <= `PLUGIN_MAX_UPLOAD_SIZE` -* zip contains `__init__.py` in first level dir -* `__init__.py` must contain valid metadata - - -* `version` must be unique whithin a plugin -* there must be one and only *last* versions in each plugin's branch - -At the time of plugin creation, the name of the folder inside the compressed file is stored in the variable `package_name`, this value must be unique and cannot be changed. `package_name` is also used to build SEF URLs, for example the plugin's page for the plugin *Hello World Plugin* with `package_name` *HelloWorld* is ``_ - -The `package_name` (and hence the first level folder inside the compressed zip file) cannot contain only ASCI letters (A-Z and a-z), digits and the characters underscore (_) and minus (-) and cannot start with a -digit. - -Example from the `HelloWorld` plugin compressed zip file:: - - Archive: plugins/tests/HelloWorld/HelloWorld_1.2.zip - Length Date Time Name - --------- ---------- ----- ---- - 0 2011-11-13 15:05 HelloWorld/ - 1304 2011-11-13 12:40 HelloWorld/icon.png - 374 2011-11-13 15:05 HelloWorld/metadata.txt - 1094 2011-11-13 12:40 HelloWorld/HelloWorld.py - 396 2011-11-13 12:40 HelloWorld/__init__.py - --------- ------- - 3168 5 files - -Metadata -======== - -You can find detailed informations about metadata in the -`PyQGIS developer cookbook `_ - - -Configuration -============= - -All values can be overridden in `settings.py` - -========================== ============= ======================= -Parameter Default Notes -========================== ============= ======================= -PLUGINS_STORAGE_PATH packages -PLUGIN_MAX_UPLOAD_SIZE 1048576 in bytes -PLUGINS_FRESH_DAYS 30 days -MAIL_FROM_ADDRESS - used in email notifications -PLUGIN_REQUIRED_METADATA [#f1]_ used in validator -PLUGIN_OPTIONAL_METADATA [#f2]_ used in validator -========================== ============= ======================= - - -Plugins XML -=========== - -Plugins XML is available at `http://plugins.qgis.org/plugins/plugins.xml` - -accepted parameters: - * qgis: qgis version - * stable_only: 0/1, default to 0 - * package_name: package name (to get all versions for the given plugin) - - -.. rubric:: Footnotes - -.. [#f1] 'name', 'description', 'version', 'qgisMinimumVersion' -.. [#f2] Supported by metadata.txt only: 'homepage', 'changelog', 'tracker', 'repository', 'tags' diff --git a/qgis-app/plugins/docs/metadata.rst b/qgis-app/plugins/docs/metadata.rst deleted file mode 100755 index 623eba09..00000000 --- a/qgis-app/plugins/docs/metadata.rst +++ /dev/null @@ -1,2 +0,0 @@ -You can find detailed informations about metadata in the -`PyQGIS developer cookbook `_ diff --git a/qgis-app/plugins/forms.py b/qgis-app/plugins/forms.py deleted file mode 100644 index 9f98f03f..00000000 --- a/qgis-app/plugins/forms.py +++ /dev/null @@ -1,272 +0,0 @@ -# i18n -import re - -from django import forms -from django.contrib.auth.models import User -from django.forms import CharField, ModelForm, ValidationError -from django.utils.safestring import mark_safe -from django.utils.translation import gettext_lazy as _ -from plugins.models import Plugin, PluginOutstandingToken, PluginVersion, PluginVersionFeedback -from plugins.validator import validator -from taggit.forms import * - - -def _clean_tags(tags): - """Return a stripped and cleaned tag list, empty tags are deleted""" - if tags: - _tags = [] - for t in tags.split(","): - if t.strip(): - _tags.append(t.strip()) - return ",".join(_tags) - return None - - -class PluginForm(ModelForm): - """ - Form for plugin editing - """ - - required_css_class = "required" - tags = TagField(required=False) - - class Meta: - model = Plugin - fields = ( - "description", - "about", - "author", - "email", - "icon", - "deprecated", - "homepage", - "tracker", - "repository", - "owners", - "maintainer", - "display_created_by", - "tags", - "server", - ) - - def __init__(self, *args, **kwargs): - super(PluginForm, self).__init__(*args, **kwargs) - self.fields['owners'].label = "Collaborators" - - choices = ( - (self.instance.created_by.pk, self.instance.created_by.username + " (Plugin creator)"), - ) - for owner in self.instance.owners.exclude(pk=self.instance.created_by.pk): - choices += ((owner.pk, owner.username + " (Collaborator)"),) - - self.fields['maintainer'].choices = choices - self.fields['maintainer'].label = "Maintainer" - - def clean(self): - """ - Check author - """ - if self.cleaned_data.get("author") and not re.match( - r"^[^/]+$", self.cleaned_data.get("author") - ): - raise ValidationError(_("Author name cannot contain slashes.")) - return super(PluginForm, self).clean() - - -class PluginVersionForm(ModelForm): - """ - Form for version upload on existing plugins - """ - - required_css_class = "required" - changelog = forms.fields.CharField( - label=_("Changelog"), - required=False, - help_text=_( - "Insert here a short description of the changes that have been made in this version. This field is not mandatory and it is automatically filled from the metadata.txt file." - ), - widget=forms.Textarea, - ) - - def __init__(self, *args, **kwargs): - kwargs.pop("is_trusted") - super(PluginVersionForm, self).__init__(*args, **kwargs) - # FIXME: check why this is not working correctly anymore - # now "approved" is removed from the form (see Meta) - # instance = getattr(self, 'instance', None) - # if instance and not is_trusted: - # self.fields['approved'].initial = False - # self.fields['approved'].widget.attrs = {'disabled':'disabled'} - # instance.approved = False - - class Meta: - model = PluginVersion - exclude = ( - "created_by", - "plugin", - "approved", - "version", - "min_qg_version", - "max_qg_version", - ) - fields = ("package", "experimental", "changelog") - - def clean(self): - """ - Only read package if uploaded - """ - # Override package - changelog = self.cleaned_data.get("changelog") - - if self.files: - package = self.cleaned_data.get("package") - try: - cleaned_data = validator(package) - if ( - "experimental" in dict(cleaned_data) - and "experimental" in self.cleaned_data - and dict(cleaned_data)["experimental"] - != self.cleaned_data["experimental"] - ): - msg = _( - "The 'experimental' flag in the form does not match the 'experimental' flag in the plugins package metadata.
" - ) - raise ValidationError(mark_safe("%s" % msg)) - self.cleaned_data.update(cleaned_data) - except ValidationError as e: - msg = _( - "There were errors reading plugin package (please check also your plugin's metadata).
" - ) - raise ValidationError( - mark_safe("%s %s" % (msg, "
".join(e.messages))) - ) - # Populate instance - self.instance.min_qg_version = self.cleaned_data.get("qgisMinimumVersion") - self.instance.max_qg_version = self.cleaned_data.get("qgisMaximumVersion") - self.instance.version = PluginVersion.clean_version( - self.cleaned_data.get("version") - ) - self.instance.server = self.cleaned_data.get("server") - - # Check plugin folder name - if ( - self.cleaned_data.get("package_name") - and self.cleaned_data.get("package_name") - != self.instance.plugin.package_name - ): - raise ValidationError( - _( - "Plugin folder name mismatch: the plugin main folder name in the compressed file (%s) is different from the original plugin package name (%s)." - ) - % ( - self.cleaned_data.get("package_name"), - self.instance.plugin.package_name, - ) - ) - # Also set changelog from metadata - if changelog: - self.cleaned_data["changelog"] = changelog - # Clean tags - self.cleaned_data["tags"] = _clean_tags(self.cleaned_data.get("tags", None)) - self.instance.changelog = self.cleaned_data.get("changelog") - return super(PluginVersionForm, self).clean() - - -class PackageUploadForm(forms.Form): - """ - Single step upload for new plugins - """ - - experimental = forms.BooleanField( - required=False, - label=_("Experimental"), - help_text=_( - "Please check this box if the plugin is experimental. Please note that this field might be overridden by metadata (if present)." - ), - ) - package = forms.FileField( - label=_("Plugin package"), - help_text=_("Please select the zipped file of the plugin."), - ) - - def clean_package(self): - """ - Populates cleaned_data with metadata from the zip package - """ - package = self.cleaned_data.get("package") - try: - self.cleaned_data.update(validator(package)) - except ValidationError as e: - msg = _( - "There were errors reading plugin package (please check also your plugin's metadata)." - ) - raise ValidationError(mark_safe("%s %s" % (msg, ",".join(e.messages)))) - # Disabled: now the PackageUploadForm also accepts updates - # if Plugin.objects.filter(package_name = self.cleaned_data['package_name']).count(): - # raise ValidationError(_('A plugin with this package name (%s) already exists. To update an existing plugin, you should open the plugin\'s details view and add a new version from there.') % self.cleaned_data['package_name']) - - # if Plugin.objects.filter(name = self.cleaned_data['name']).count(): - # raise ValidationError(_('A plugin with this name (%s) already exists.') % self.cleaned_data['name']) - self.cleaned_data["version"] = PluginVersion.clean_version( - self.cleaned_data["version"] - ) - # Checks for version - if Plugin.objects.filter( - package_name=self.cleaned_data["package_name"], - pluginversion__version=self.cleaned_data["version"], - ).count(): - raise ValidationError( - _( - "A plugin with this name (%s) and version number (%s) already exists." - ) - % (self.cleaned_data["name"], self.cleaned_data["version"]) - ) - # Clean tags - self.cleaned_data["tags"] = _clean_tags(self.cleaned_data.get("tags", None)) - return package - - -class VersionFeedbackForm(forms.Form): - """Feedback for a plugin version""" - - feedback = forms.CharField( - widget=forms.Textarea( - attrs={ - "placeholder": _( - "Please provide clear feedback as a task. \n" - "You can create multiple tasks with '- [ ]'.\n" - "e.g:\n" - "- [ ] first task\n" - "- [ ] second task" - ), - "rows": "5", - "class": "span12" - } - ) - ) - - def clean(self): - super().clean() - feedback = self.cleaned_data.get('feedback') - - if feedback: - lines: list = feedback.split('\n') - bullet_points: list = [ - line[6:].strip() for line in lines if line.strip().startswith('- [ ]') - ] - has_bullet_point = len(bullet_points) >= 1 - tasks: list = bullet_points if has_bullet_point else [feedback] - self.cleaned_data['tasks'] = tasks - - return self.cleaned_data - -class PluginTokenForm(ModelForm): - """ - Form for token description editing - """ - - class Meta: - model = PluginOutstandingToken - fields = ( - "description", - ) \ No newline at end of file diff --git a/qgis-app/plugins/management/commands/cleanmediafolder.py b/qgis-app/plugins/management/commands/cleanmediafolder.py deleted file mode 100644 index da18bda9..00000000 --- a/qgis-app/plugins/management/commands/cleanmediafolder.py +++ /dev/null @@ -1,38 +0,0 @@ -import os -from shutil import rmtree -from django.conf import settings -from django.core.management import call_command -from django.core.management.base import BaseCommand - -class Command(BaseCommand): - help = 'Run collectstatic and delete folders from media that exist in static' - - def handle(self, *args, **options): - confirm = input("Do you want to run 'collectstatic' first? (yes/no): ") - if confirm.lower() == 'yes': - # Run collectstatic command - call_command('collectstatic', interactive=False) - - # Get the paths of static and media folders - static_root = settings.STATIC_ROOT - media_root = settings.MEDIA_ROOT - - # Iterate over each directory in the static folder - for static_dir in os.listdir(static_root): - static_path = os.path.join(static_root, static_dir) - - # Check if the path is a directory and exists in the media folder - if os.path.isdir(static_path) and os.path.exists(os.path.join(media_root, static_dir)): - confirm = input(f"Are you sure you want to delete '{static_dir}' from the media folder? (yes/no): ") - - if confirm.lower() == 'yes': - try: - # Delete the corresponding folder in the media folder - rmtree(os.path.join(media_root, static_dir)) - self.stdout.write(self.style.SUCCESS(f'Deleted {static_dir} from media folder.')) - except Exception as e: - self.stderr.write(self.style.ERROR(f'Error deleting {static_dir}: {str(e)}')) - else: - self.stdout.write(self.style.WARNING(f'Skipped deletion of {static_dir}.')) - - self.stdout.write(self.style.SUCCESS('The media folder has been cleaned.')) diff --git a/qgis-app/plugins/management/commands/generate_plugins_xml.py b/qgis-app/plugins/management/commands/generate_plugins_xml.py deleted file mode 100644 index 1795dcf9..00000000 --- a/qgis-app/plugins/management/commands/generate_plugins_xml.py +++ /dev/null @@ -1,21 +0,0 @@ -# -*- coding: utf-8 -*- -from django.core.management.base import BaseCommand -from plugins.tasks.generate_plugins_xml import generate_plugins_xml -from django.conf import settings - -class Command(BaseCommand): - - help = "Fetch and cached plugins xml" - - def add_arguments(self, parser): - parser.add_argument( - "-s", - "--site", - dest="site", - default=settings.DEFAULT_PLUGINS_SITE, - help="Site url to get the source of plugins", - ) - - def handle(self, *args, **options): - site = options.get("site") - generate_plugins_xml.delay(site=site) diff --git a/qgis-app/plugins/management/commands/organize_old_package.py b/qgis-app/plugins/management/commands/organize_old_package.py deleted file mode 100644 index 991ed958..00000000 --- a/qgis-app/plugins/management/commands/organize_old_package.py +++ /dev/null @@ -1,40 +0,0 @@ -# myapp/management/commands/organize_packages.py -import os -import shutil -from django.core.management.base import BaseCommand -from django.conf import settings -from plugins.models import PluginVersion - -PLUGINS_STORAGE_PATH = getattr(settings, "PLUGINS_STORAGE_PATH", "packages") -class Command(BaseCommand): - help = 'Organize packages created before 2014 into folders by year' - - def handle(self, *args, **options): - packages_dir = os.path.join(settings.MEDIA_ROOT, PLUGINS_STORAGE_PATH) - - # Some of the packages created on 2014 also need to be organized - versions = PluginVersion.objects.filter(created_on__lt='2014-12-31').exclude(package__icontains='2014/') - self.stdout.write(self.style.NOTICE(f'{versions.count()} packages will be organized.')) - - for version in versions: - year_folder = os.path.join(packages_dir, str(version.created_on.year)) - - # Create the year folder if it doesn't exist - os.makedirs(year_folder, exist_ok=True) - - # Copy the package file to the year folder - old_path = version.package.path - if os.path.exists(old_path): - new_path = os.path.join(year_folder, os.path.basename(old_path)) - if not os.path.exists(new_path): - shutil.copy(old_path, year_folder) - - # Update the model with the new package path - version.package.name = os.path.relpath(new_path, settings.MEDIA_ROOT) - version.save() - else: - self.stdout.write(self.style.WARNING(f'Plugin version id {version.pk} ignored: {new_path} already exists.')) - else: - self.stdout.write(self.style.WARNING(f'Plugin version id {version.pk} ignored: {old_path} is not found.')) - - self.stdout.write(self.style.SUCCESS('Packages organized successfully')) diff --git a/qgis-app/plugins/migrations/0001_initial.py b/qgis-app/plugins/migrations/0001_initial.py deleted file mode 100644 index 41119f5b..00000000 --- a/qgis-app/plugins/migrations/0001_initial.py +++ /dev/null @@ -1,260 +0,0 @@ -# Generated by Django 2.1.7 on 2019-03-12 04:30 - -import django.db.models.deletion -import plugins.models -import taggit_autosuggest.managers -from django.conf import settings -from django.db import migrations, models - - -class Migration(migrations.Migration): - - initial = True - - dependencies = [ - ("taggit", "0001_initial"), - migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ] - - operations = [ - migrations.CreateModel( - name="Plugin", - fields=[ - ( - "id", - models.AutoField( - auto_created=True, - primary_key=True, - serialize=False, - verbose_name="ID", - ), - ), - ( - "created_on", - models.DateTimeField(auto_now_add=True, verbose_name="Created on"), - ), - ( - "modified_on", - models.DateTimeField(editable=False, verbose_name="Modified on"), - ), - ( - "author", - models.CharField( - help_text="This is the plugin's original author, if different from the uploader, this field will appear in the XML and in the web GUI", - max_length=256, - verbose_name="Author", - ), - ), - ( - "email", - models.EmailField(max_length=254, verbose_name="Author email"), - ), - ( - "homepage", - models.URLField( - blank=True, null=True, verbose_name="Plugin homepage" - ), - ), - ( - "repository", - models.URLField(null=True, verbose_name="Code repository"), - ), - ("tracker", models.URLField(null=True, verbose_name="Tracker")), - ( - "package_name", - models.CharField( - editable=False, - help_text="This is the plugin's internal name, equals to the main folder name", - max_length=256, - unique=True, - verbose_name="Package Name", - ), - ), - ( - "name", - models.CharField( - help_text="Must be unique", - max_length=256, - unique=True, - verbose_name="Name", - ), - ), - ("description", models.TextField(verbose_name="Description")), - ("about", models.TextField(null=True, verbose_name="About")), - ( - "icon", - models.ImageField( - blank=True, - null=True, - upload_to="packages/%Y", - verbose_name="Icon", - ), - ), - ( - "downloads", - models.IntegerField( - default=0, editable=False, verbose_name="Downloads" - ), - ), - ( - "featured", - models.BooleanField( - db_index=True, default=False, verbose_name="Featured" - ), - ), - ( - "deprecated", - models.BooleanField( - db_index=True, default=False, verbose_name="Deprecated" - ), - ), - ( - "server", - models.BooleanField( - db_index=True, default=False, verbose_name="Server" - ), - ), - ( - "rating_votes", - models.PositiveIntegerField(blank=True, default=0, editable=False), - ), - ( - "rating_score", - models.IntegerField(blank=True, default=0, editable=False), - ), - ( - "created_by", - models.ForeignKey( - on_delete=django.db.models.deletion.CASCADE, - related_name="plugins_created_by", - to=settings.AUTH_USER_MODEL, - verbose_name="Created by", - ), - ), - ( - "owners", - models.ManyToManyField(blank=True, to=settings.AUTH_USER_MODEL), - ), - ( - "tags", - taggit_autosuggest.managers.TaggableManager( - blank=True, - help_text="A comma-separated list of tags.", - through="taggit.TaggedItem", - to="taggit.Tag", - verbose_name="Tags", - ), - ), - ], - options={ - "ordering": ("name",), - "permissions": (("can_approve", "Can approve plugins versions"),), - }, - ), - migrations.CreateModel( - name="PluginVersion", - fields=[ - ( - "id", - models.AutoField( - auto_created=True, - primary_key=True, - serialize=False, - verbose_name="ID", - ), - ), - ( - "created_on", - models.DateTimeField(auto_now_add=True, verbose_name="Created on"), - ), - ( - "downloads", - models.IntegerField( - default=0, editable=False, verbose_name="Downloads" - ), - ), - ( - "min_qg_version", - plugins.models.QGVersionZeroForcedField( - db_index=True, - max_length=32, - verbose_name="Minimum QGIS version", - ), - ), - ( - "max_qg_version", - plugins.models.QGVersionZeroForcedField( - blank=True, - db_index=True, - max_length=32, - null=True, - verbose_name="Maximum QGIS version", - ), - ), - ( - "version", - plugins.models.VersionField( - db_index=True, max_length=32, verbose_name="Version" - ), - ), - ( - "changelog", - models.TextField(blank=True, null=True, verbose_name="Changelog"), - ), - ( - "package", - models.FileField( - upload_to="packages/%Y", verbose_name="Plugin package" - ), - ), - ( - "experimental", - models.BooleanField( - db_index=True, - default=False, - help_text="Check this box if this version is experimental, leave unchecked if it's stable. Please note that this field might be overridden by metadata (if present).", - verbose_name="Experimental flag", - ), - ), - ( - "approved", - models.BooleanField( - db_index=True, - default=True, - help_text="Set to false if you wish to unapprove the plugin version.", - verbose_name="Approved", - ), - ), - ( - "external_deps", - models.CharField( - help_text="PIP install string", - max_length=512, - null=True, - verbose_name="External dependencies", - ), - ), - ( - "created_by", - models.ForeignKey( - on_delete=django.db.models.deletion.CASCADE, - to=settings.AUTH_USER_MODEL, - verbose_name="Created by", - ), - ), - ( - "plugin", - models.ForeignKey( - on_delete=django.db.models.deletion.CASCADE, to="plugins.Plugin" - ), - ), - ], - options={ - "ordering": ("plugin", "-version", "experimental"), - }, - ), - migrations.AlterUniqueTogether( - name="pluginversion", - unique_together={("plugin", "version")}, - ), - ] diff --git a/qgis-app/plugins/migrations/0002_plugins_feedback.py b/qgis-app/plugins/migrations/0002_plugins_feedback.py deleted file mode 100644 index 76f760a3..00000000 --- a/qgis-app/plugins/migrations/0002_plugins_feedback.py +++ /dev/null @@ -1,31 +0,0 @@ -# Generated by Django 2.2.25 on 2023-06-17 03:19 - -from django.conf import settings -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ('plugins', '0001_initial'), - ] - - operations = [ - migrations.CreateModel( - name='PluginVersionFeedback', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('task', models.TextField(help_text='A feedback task. Please write your review as a task for this plugin.', max_length=1000, verbose_name='Task')), - ('created_on', models.DateTimeField(auto_now_add=True, verbose_name='Created on')), - ('completed_on', models.DateTimeField(blank=True, null=True, verbose_name='Completed on')), - ('is_completed', models.BooleanField(db_index=True, default=False, verbose_name='Completed')), - ('reviewer', models.ForeignKey(help_text='The user who reviewed this plugin.', on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='Reviewed by')), - ('version', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='feedback', to='plugins.PluginVersion')), - ], - options={ - 'ordering': ['created_on'], - }, - ), - ] diff --git a/qgis-app/plugins/migrations/0002_pluginversiondownload.py b/qgis-app/plugins/migrations/0002_pluginversiondownload.py deleted file mode 100644 index 6958c874..00000000 --- a/qgis-app/plugins/migrations/0002_pluginversiondownload.py +++ /dev/null @@ -1,27 +0,0 @@ -# Generated by Django 2.2.25 on 2023-06-29 03:29 - -from django.db import migrations, models -import django.db.models.deletion -import django.utils.timezone - - -class Migration(migrations.Migration): - - dependencies = [ - ('plugins', '0001_initial'), - ] - - operations = [ - migrations.CreateModel( - name='PluginVersionDownload', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('download_date', models.DateField(default=django.utils.timezone.now)), - ('download_count', models.IntegerField(default=0)), - ('plugin_version', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='plugins.PluginVersion')), - ], - options={ - 'unique_together': {('plugin_version', 'download_date')}, - }, - ), - ] diff --git a/qgis-app/plugins/migrations/0003_plugin_allow_update_name.py b/qgis-app/plugins/migrations/0003_plugin_allow_update_name.py deleted file mode 100644 index dac62f7f..00000000 --- a/qgis-app/plugins/migrations/0003_plugin_allow_update_name.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 2.2.25 on 2023-11-07 03:04 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('plugins', '0002_pluginversiondownload'), - ] - - operations = [ - migrations.AddField( - model_name='plugin', - name='allow_update_name', - field=models.BooleanField(default=False, help_text='Allow name in metadata.txt to update the plugin name', verbose_name='Allow update name'), - ), - ] diff --git a/qgis-app/plugins/migrations/0004_merge_20231122_0223.py b/qgis-app/plugins/migrations/0004_merge_20231122_0223.py deleted file mode 100644 index 8251bc87..00000000 --- a/qgis-app/plugins/migrations/0004_merge_20231122_0223.py +++ /dev/null @@ -1,14 +0,0 @@ -# Generated by Django 2.2.25 on 2023-11-30 05:13 - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('plugins', '0002_plugins_feedback'), - ('plugins', '0003_plugin_allow_update_name'), - ] - - operations = [ - ] diff --git a/qgis-app/plugins/migrations/0004_merge_20231123_0018.py b/qgis-app/plugins/migrations/0004_merge_20231123_0018.py deleted file mode 100644 index 8e18926d..00000000 --- a/qgis-app/plugins/migrations/0004_merge_20231123_0018.py +++ /dev/null @@ -1,14 +0,0 @@ -# Generated by Django 3.2.11 on 2023-11-23 00:18 - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('plugins', '0002_plugins_feedback'), - ('plugins', '0003_plugin_allow_update_name'), - ] - - operations = [ - ] diff --git a/qgis-app/plugins/migrations/0005_auto_20231214_2317.py b/qgis-app/plugins/migrations/0005_auto_20231214_2317.py deleted file mode 100644 index 8cd7045b..00000000 --- a/qgis-app/plugins/migrations/0005_auto_20231214_2317.py +++ /dev/null @@ -1,23 +0,0 @@ -# Generated by Django 2.2.25 on 2023-12-14 23:17 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('plugins', '0004_merge_20231122_0223'), - ] - - operations = [ - migrations.AddField( - model_name='pluginversiondownload', - name='country_code', - field=models.CharField(default='N/D', max_length=3), - ), - migrations.AddField( - model_name='pluginversiondownload', - name='country_name', - field=models.CharField(default='N/D', max_length=100), - ), - ] diff --git a/qgis-app/plugins/migrations/0005_plugin_maintainer.py b/qgis-app/plugins/migrations/0005_plugin_maintainer.py deleted file mode 100644 index dfeffa0b..00000000 --- a/qgis-app/plugins/migrations/0005_plugin_maintainer.py +++ /dev/null @@ -1,29 +0,0 @@ -# Generated by Django 2.2.25 on 2023-11-29 22:45 - -from django.conf import settings -from django.db import migrations, models -import django.db.models.deletion - -def populate_maintainer(apps, schema_editor): - Plugin = apps.get_model('plugins', 'Plugin') - - # Set the maintainer as the plugin creator by default - for obj in Plugin.objects.all(): - obj.maintainer = obj.created_by - obj.save() - -class Migration(migrations.Migration): - - dependencies = [ - migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ('plugins', '0004_merge_20231122_0223'), - ] - - operations = [ - migrations.AddField( - model_name='plugin', - name='maintainer', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='plugins_maintainer', to=settings.AUTH_USER_MODEL, verbose_name='Maintainer'), - ), - migrations.RunPython(populate_maintainer), - ] diff --git a/qgis-app/plugins/migrations/0005_pluginoutstandingtoken.py b/qgis-app/plugins/migrations/0005_pluginoutstandingtoken.py deleted file mode 100644 index 4e406433..00000000 --- a/qgis-app/plugins/migrations/0005_pluginoutstandingtoken.py +++ /dev/null @@ -1,24 +0,0 @@ -# Generated by Django 2.2.25 on 2023-12-11 23:36 - -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - ('token_blacklist', '0007_auto_20171017_2214'), - ('plugins', '0004_merge_20231122_0223'), - ] - - operations = [ - migrations.CreateModel( - name='PluginOutstandingToken', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('is_blacklisted', models.BooleanField(default=False)), - ('plugin', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='plugins.Plugin')), - ('token', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='token_blacklist.OutstandingToken')), - ], - ), - ] diff --git a/qgis-app/plugins/migrations/0006_auto_20231218_0225.py b/qgis-app/plugins/migrations/0006_auto_20231218_0225.py deleted file mode 100644 index 60d0af5f..00000000 --- a/qgis-app/plugins/migrations/0006_auto_20231218_0225.py +++ /dev/null @@ -1,28 +0,0 @@ -# Generated by Django 2.2.25 on 2023-12-18 02:25 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('plugins', '0005_pluginoutstandingtoken'), - ] - - operations = [ - migrations.AddField( - model_name='pluginoutstandingtoken', - name='description', - field=models.CharField(blank=True, help_text="Describe this token so that it's easier to remember where you're using it.", max_length=512, null=True, verbose_name='Description'), - ), - migrations.AddField( - model_name='pluginoutstandingtoken', - name='is_newly_created', - field=models.BooleanField(default=False), - ), - migrations.AddField( - model_name='pluginoutstandingtoken', - name='last_used_on', - field=models.DateTimeField(blank=True, null=True, verbose_name='Last used on'), - ), - ] diff --git a/qgis-app/plugins/migrations/0006_plugin_display_created_by.py b/qgis-app/plugins/migrations/0006_plugin_display_created_by.py deleted file mode 100644 index 85af8484..00000000 --- a/qgis-app/plugins/migrations/0006_plugin_display_created_by.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 2.2.25 on 2023-11-29 23:22 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('plugins', '0005_plugin_maintainer'), - ] - - operations = [ - migrations.AddField( - model_name='plugin', - name='display_created_by', - field=models.BooleanField(default=False, verbose_name='Display "Created by" in plugin details'), - ), - ] diff --git a/qgis-app/plugins/migrations/0007_auto_20240109_0428.py b/qgis-app/plugins/migrations/0007_auto_20240109_0428.py deleted file mode 100644 index 6d6af2da..00000000 --- a/qgis-app/plugins/migrations/0007_auto_20240109_0428.py +++ /dev/null @@ -1,30 +0,0 @@ -# Generated by Django 2.2.25 on 2024-01-09 04:28 - -from django.conf import settings -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - ('plugins', '0006_auto_20231218_0225'), - ] - - operations = [ - migrations.AddField( - model_name='pluginversion', - name='is_from_token', - field=models.BooleanField(default=False, verbose_name='Is uploaded using token'), - ), - migrations.AddField( - model_name='pluginversion', - name='token', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='plugins.PluginOutstandingToken', verbose_name='Token used'), - ), - migrations.AlterField( - model_name='pluginversion', - name='created_by', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='Created by'), - ), - ] diff --git a/qgis-app/plugins/migrations/0008_merge_20240206_0448.py b/qgis-app/plugins/migrations/0008_merge_20240206_0448.py deleted file mode 100644 index ad09b265..00000000 --- a/qgis-app/plugins/migrations/0008_merge_20240206_0448.py +++ /dev/null @@ -1,14 +0,0 @@ -# Generated by Django 2.2.25 on 2024-02-04 23:16 - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('plugins', '0007_auto_20240109_0428'), - ('plugins', '0006_plugin_display_created_by'), - ] - - operations = [ - ] diff --git a/qgis-app/plugins/migrations/0009_merge_20240321_0207.py b/qgis-app/plugins/migrations/0009_merge_20240321_0207.py deleted file mode 100644 index 45cd685a..00000000 --- a/qgis-app/plugins/migrations/0009_merge_20240321_0207.py +++ /dev/null @@ -1,14 +0,0 @@ -# Generated by Django 4.2.11 on 2024-03-21 02:07 - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('plugins', '0004_merge_20231123_0018'), - ('plugins', '0008_merge_20240206_0448'), - ] - - operations = [ - ] diff --git a/qgis-app/plugins/migrations/0010_merge_20240517_0729.py b/qgis-app/plugins/migrations/0010_merge_20240517_0729.py deleted file mode 100644 index 54355764..00000000 --- a/qgis-app/plugins/migrations/0010_merge_20240517_0729.py +++ /dev/null @@ -1,14 +0,0 @@ -# Generated by Django 4.2.13 on 2024-05-17 07:29 - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('plugins', '0005_auto_20231214_2317'), - ('plugins', '0009_merge_20240321_0207'), - ] - - operations = [ - ] diff --git a/qgis-app/plugins/migrations/0011_alter_pluginversiondownload_unique_together.py b/qgis-app/plugins/migrations/0011_alter_pluginversiondownload_unique_together.py deleted file mode 100644 index fbd85028..00000000 --- a/qgis-app/plugins/migrations/0011_alter_pluginversiondownload_unique_together.py +++ /dev/null @@ -1,17 +0,0 @@ -# Generated by Django 4.2.13 on 2024-05-28 06:47 - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('plugins', '0010_merge_20240517_0729'), - ] - - operations = [ - migrations.AlterUniqueTogether( - name='pluginversiondownload', - unique_together={('plugin_version', 'download_date', 'country_code', 'country_name')}, - ), - ] diff --git a/qgis-app/plugins/migrations/0012_pluginversionfeedback_modified_on.py b/qgis-app/plugins/migrations/0012_pluginversionfeedback_modified_on.py deleted file mode 100644 index 2e637f4e..00000000 --- a/qgis-app/plugins/migrations/0012_pluginversionfeedback_modified_on.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 4.2.13 on 2024-06-13 08:06 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('plugins', '0011_alter_pluginversiondownload_unique_together'), - ] - - operations = [ - migrations.AddField( - model_name='pluginversionfeedback', - name='modified_on', - field=models.DateTimeField(blank=True, editable=False, null=True, verbose_name='Modified on'), - ), - ] diff --git a/qgis-app/plugins/migrations/__init__.py b/qgis-app/plugins/migrations/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/qgis-app/plugins/models.py b/qgis-app/plugins/models.py deleted file mode 100644 index 5b4a7ff2..00000000 --- a/qgis-app/plugins/models.py +++ /dev/null @@ -1,1058 +0,0 @@ -# -*- coding: utf-8 -*- - -import datetime -import os -import re - -from django.conf import settings -from django.contrib.auth.models import User -from django.db import models -from django.urls import reverse -from django.utils.translation import gettext_lazy as _ -from django.utils import timezone -from djangoratings.fields import AnonymousRatingField -from taggit_autosuggest.managers import TaggableManager -from rest_framework_simplejwt.token_blacklist.models import OutstandingToken - -from django.db.models import OuterRef, Count, Subquery, F - -PLUGINS_STORAGE_PATH = getattr(settings, "PLUGINS_STORAGE_PATH", "packages/%Y") -PLUGINS_FRESH_DAYS = getattr(settings, "PLUGINS_FRESH_DAYS", 30) - - -# Used in Version fields to transform DB value back to human readable string -# Allows "-" for processing plugin -VERSION_RE = r"(^|(?<=\.))0+(?!(\.|$|-))|\.#+" - - -class BasePluginManager(models.Manager): - """ - Adds a score - """ - - def get_queryset(self): - return ( - super(BasePluginManager, self) - .get_queryset() - .extra( - select={ - "average_vote": "rating_score/(rating_votes+0.001)", - "latest_version_date": ( - "SELECT created_on FROM plugins_pluginversion WHERE " - "plugins_pluginversion.plugin_id = plugins_plugin.id " - "AND approved = TRUE " - "ORDER BY created_on DESC LIMIT 1" - ), - } - ) - ) - - -class ApprovedPlugins(BasePluginManager): - """ - Shows only public plugins: i.e. those with - and with at least one approved version ("stable" or "experimental") - """ - - def get_queryset(self): - return ( - super(ApprovedPlugins, self) - .get_queryset() - .filter(pluginversion__approved=True) - .distinct() - ) - - -class StablePlugins(BasePluginManager): - """ - Shows only public plugins: i.e. those with "approved" flag set - and with one "stable" version - """ - - def get_queryset(self): - return ( - super(StablePlugins, self) - .get_queryset() - .filter(pluginversion__approved=True, pluginversion__experimental=False) - .distinct() - ) - - -class ExperimentalPlugins(BasePluginManager): - """ - Shows only public plugins: i.e. those with "approved" flag set - and with one "experimental" version - """ - - def get_queryset(self): - return ( - super(ExperimentalPlugins, self) - .get_queryset() - .filter(pluginversion__approved=True, pluginversion__experimental=True) - .distinct() - ) - - -class FeaturedPlugins(BasePluginManager): - """ - Shows only public featured stable plugins: i.e. those with "approved" flag set - and "featured" flag set - """ - - def get_queryset(self): - return ( - super(FeaturedPlugins, self) - .get_queryset() - .filter(pluginversion__approved=True, featured=True) - .order_by("-created_on") - .distinct() - ) - - -class FreshPlugins(BasePluginManager): - """ - Shows only approved plugins: i.e. those with "approved" version flag set - and created less than "days" ago. - """ - - def __init__(self, days=PLUGINS_FRESH_DAYS, *args, **kwargs): - self.days = days - return super(FreshPlugins, self).__init__(*args, **kwargs) - - def get_queryset(self): - return ( - super(FreshPlugins, self) - .get_queryset() - .filter( - deprecated=False, - pluginversion__approved=True, - created_on__gte=datetime.datetime.now() - - datetime.timedelta(days=self.days), - ) - .order_by("-created_on") - .distinct() - ) - - -class LatestPlugins(BasePluginManager): - """ - Shows only approved plugins ordered descending by latest_version - and the latest_version - """ - - def __init__(self, days=PLUGINS_FRESH_DAYS, *args, **kwargs): - self.days = days - return super(LatestPlugins, self).__init__(*args, **kwargs) - - def get_queryset(self): - return ( - super(LatestPlugins, self) - .get_queryset() - .filter( - deprecated=False, - pluginversion__approved=True, - pluginversion__created_on__gte=( - datetime.datetime.now() - datetime.timedelta(days=self.days) - ), - ) - .order_by("-latest_version_date") - .distinct() - ) - - -class UnapprovedPlugins(BasePluginManager): - """ - Shows only unapproved and not deprecated plugins - """ - - def get_queryset(self): - return ( - super(UnapprovedPlugins, self) - .get_queryset() - .filter(pluginversion__approved=False, deprecated=False) - .extra( - select={ - "average_vote": "rating_score/(rating_votes+0.001)", - "latest_version_date": ( - "SELECT created_on FROM plugins_pluginversion WHERE " - "plugins_pluginversion.plugin_id = plugins_plugin.id " - "ORDER BY created_on DESC LIMIT 1" - ), - } - ) - .distinct() - ) - - -class DeprecatedPlugins(BasePluginManager): - """ - Shows only deprecated plugins - """ - - def get_queryset(self): - return ( - super(DeprecatedPlugins, self) - .get_queryset() - .filter(deprecated=True) - .distinct() - ) - - -class PopularPlugins(ApprovedPlugins): - """ - Shows only approved plugins, sort by popularity algorithm - """ - - def get_queryset(self): - return ( - super(PopularPlugins, self) - .get_queryset() - .filter(deprecated=False) - .extra( - select={ - "popularity": "plugins_plugin.downloads * (1 + (rating_score/(rating_votes+0.01)/3))" - } - ) - .order_by("-popularity") - .distinct() - ) - - -class MostDownloadedPlugins(ApprovedPlugins): - """ - Shows only approved plugins, sort by downloads - """ - - def get_queryset(self): - return ( - super(MostDownloadedPlugins, self) - .get_queryset() - .filter(deprecated=False) - .order_by("-downloads") - .distinct() - ) - - -class MostVotedPlugins(ApprovedPlugins): - """ - Shows only approved plugins, sort by vote number - """ - - def get_queryset(self): - return ( - super(MostVotedPlugins, self) - .get_queryset() - .filter(deprecated=False) - .order_by("-rating_votes") - .distinct() - ) - - -class MostRatedPlugins(ApprovedPlugins): - """ - Shows only approved plugins, sort by vote/number of votes number - """ - - def get_queryset(self): - return ( - super(MostRatedPlugins, self) - .get_queryset() - .filter(deprecated=False) - .order_by("-average_vote") - .distinct() - ) - - -class TaggablePlugins(TaggableManager): - """ - Shows only public plugins: i.e. those with "approved" flag set - """ - - def get_queryset(self): - return ( - super(TaggablePlugins, self) - .get_queryset() - .filter(deprecated=False, pluginversion__approved=True) - .distinct() - ) - - -class ServerPlugins(ApprovedPlugins): - """ - Shows only Server plugins - """ - - def get_queryset(self): - return super(ServerPlugins, self).get_queryset().filter(server=True).distinct() - - -class FeedbackCompletedPlugins(models.Manager): - """ - Show only unapproved plugins with resolved feedbacks - """ - def get_queryset(self): - feedback_count_subquery = PluginVersionFeedback.objects.filter( - version=OuterRef('pluginversion'), - is_completed=True - ).values('version').annotate( - completed_count=Count('id') - ).values('completed_count') - - return ( - super(FeedbackCompletedPlugins, self) - .get_queryset() - .filter( - pluginversion__approved=False, - deprecated=False - ) - .annotate( - total_feedback_count=Count('pluginversion__feedback'), - completed_feedback_count=Subquery(feedback_count_subquery) - ) - .filter( - total_feedback_count=F('completed_feedback_count') - ) - .extra( - select={ - "average_vote": "rating_score/(rating_votes+0.001)", - "latest_version_date": ( - "SELECT created_on FROM plugins_pluginversion WHERE " - "plugins_pluginversion.plugin_id = plugins_plugin.id " - "ORDER BY created_on DESC LIMIT 1" - ), - } - ).distinct() - ) - -class FeedbackReceivedPlugins(models.Manager): - """ - Show only unapproved plugins with a pending feedback - """ - def get_queryset(self): - feedback_count_subquery = PluginVersionFeedback.objects.filter( - version=OuterRef('pluginversion'), - is_completed=False - ).values('version').annotate( - received_count=Count('id') - ).values('received_count') - - return ( - super(FeedbackReceivedPlugins, self) - .get_queryset() - .filter( - pluginversion__approved=False, - deprecated=False - ) - .annotate( - received_feedback_count=Subquery(feedback_count_subquery) - ) - .filter( - received_feedback_count__gte=1 - ) - .extra( - select={ - "average_vote": "rating_score/(rating_votes+0.001)", - "latest_version_date": ( - "SELECT created_on FROM plugins_pluginversion WHERE " - "plugins_pluginversion.plugin_id = plugins_plugin.id " - "ORDER BY created_on DESC LIMIT 1" - ), - } - ).distinct() - ) - - -class FeedbackPendingPlugins(models.Manager): - """ - Show only unapproved plugins with a feedback - """ - def get_queryset(self): - return ( - super(FeedbackPendingPlugins, self) - .get_queryset() - .filter( - pluginversion__approved=False, - deprecated=False - ) - .annotate( - total_feedback_count=Count('pluginversion__feedback'), - ) - .filter( - total_feedback_count=0 - ) - .extra( - select={ - "average_vote": "rating_score/(rating_votes+0.001)", - "latest_version_date": ( - "SELECT created_on FROM plugins_pluginversion WHERE " - "plugins_pluginversion.plugin_id = plugins_plugin.id " - "ORDER BY created_on DESC LIMIT 1" - ), - } - ).distinct() - ) - - - -class Plugin(models.Model): - """ - Plugins model - """ - - # dates - created_on = models.DateTimeField( - _("Created on"), auto_now_add=True, editable=False - ) - modified_on = models.DateTimeField(_("Modified on"), editable=False) - - # owners - created_by = models.ForeignKey( - User, - verbose_name=_("Created by"), - related_name="plugins_created_by", - on_delete=models.CASCADE, - ) - - # maintainer - maintainer = models.ForeignKey( - User, - verbose_name=_("Maintainer"), - related_name="plugins_maintainer", - on_delete=models.CASCADE, - blank=True, - null=True - ) - - display_created_by = models.BooleanField( - _('Display "Created by" in plugin details'), - default=False - ) - - author = models.CharField( - _("Author"), - help_text=_( - "This is the plugin's original author, if different from the uploader, this field will appear in the XML and in the web GUI" - ), - max_length=256, - ) - email = models.EmailField(_("Author email")) - homepage = models.URLField(_("Plugin homepage"), blank=True, null=True) - # Support - repository = models.URLField(_("Code repository"), blank=False, null=True) - tracker = models.URLField(_("Tracker"), blank=False, null=True) - - owners = models.ManyToManyField(User, blank=True) - - # name, desc etc. - package_name = models.CharField( - _("Package Name"), - help_text=_( - "This is the plugin's internal name, equals to the main folder name" - ), - max_length=256, - unique=True, - editable=False, - ) - name = models.CharField( - _("Name"), help_text=_("Must be unique"), max_length=256, unique=True - ) - - allow_update_name = models.BooleanField( - _("Allow update name"), - help_text=_("Allow name in metadata.txt to update the plugin name"), - default=False - ) - - description = models.TextField(_("Description")) - about = models.TextField(_("About"), blank=False, null=True) - - icon = models.ImageField( - _("Icon"), blank=True, null=True, upload_to=PLUGINS_STORAGE_PATH - ) - - # downloads (soft trigger from versions) - downloads = models.IntegerField(_("Downloads"), default=0, editable=False) - - # Flags - featured = models.BooleanField(_("Featured"), default=False, db_index=True) - deprecated = models.BooleanField(_("Deprecated"), default=False, db_index=True) - - # True if the plugin has a server interface - server = models.BooleanField(_("Server"), default=False, db_index=True) - - # Managers - objects = models.Manager() - base_objects = BasePluginManager() - approved_objects = ApprovedPlugins() - stable_objects = StablePlugins() - experimental_objects = ExperimentalPlugins() - featured_objects = FeaturedPlugins() - fresh_objects = FreshPlugins() - latest_objects = LatestPlugins() - unapproved_objects = UnapprovedPlugins() - deprecated_objects = DeprecatedPlugins() - popular_objects = PopularPlugins() - most_downloaded_objects = MostDownloadedPlugins() - most_voted_objects = MostVotedPlugins() - most_rated_objects = MostRatedPlugins() - server_objects = ServerPlugins() - feedback_completed_objects = FeedbackCompletedPlugins() - feedback_received_objects = FeedbackReceivedPlugins() - feedback_pending_objects = FeedbackPendingPlugins() - - rating = AnonymousRatingField( - range=5, use_cookies=True, can_change_vote=True, allow_delete=True - ) - - tags = TaggableManager(blank=True) - - @property - def approved(self): - """ - Returns True if the plugin has at least one approved version - """ - return self.pluginversion_set.filter(approved=True).count() > 0 - - @property - def trusted(self): - """ - Returns True if the plugin's author has plugins.can_approve permission - Purpose of this decorator is to show/hide buttons in the template - """ - return self.created_by.has_perm("plugins.can_approve") - - @property - def stable(self): - """ - Returns the latest stable and approved version - """ - try: - return self.pluginversion_set.filter( - approved=True, experimental=False - ).order_by("-version")[0] - except: - return None - - @property - def experimental(self): - """ - Returns the latest experimental and approved version - """ - try: - return self.pluginversion_set.filter( - approved=True, experimental=True - ).order_by("-version")[0] - except: - return None - - @property - def editors(self): - """ - Returns a list of users that can edit the plugin: creator and owners - """ - l = [o for o in self.owners.all()] - l.append(self.created_by) - return l - - @property - def approvers(self): - """ - Returns a list of editor users that can approve a version - """ - return [l for l in self.editors if l.has_perm("plugins.can_approve")] - - @property - def avg_vote(self): - """ - Returns the rating_score/(rating_votes+0.001) value, this - calculation is also available in manager's queries as - "average_vote". - This property is still useful when the object is not loaded - through a manager, for example in related objects. - """ - return self.rating_score / (self.rating_votes + 0.001) - - class Meta: - ordering = ("name",) - # ABP: Note: this permission should belong to the - # PluginVersion class. I left it here because it - # doesn't really matters where it is. Just be - # sure you query for it using the 'plugins' class - # instead of the 'pluginversion' class. - permissions = (("can_approve", "Can approve plugins versions"),) - - def get_absolute_url(self): - return reverse("plugin_detail", args=(self.package_name,)) - - def __unicode__(self): - return "[%s] %s" % (self.pk, self.name) - - def __str__(self): - return self.__unicode__() - - def clean(self): - """ - Validates: - - * Checks that package_name respect regexp [A-Za-z][A-Za-z0-9-_]+ - * checks for case-insensitive unique package_name - """ - from django.core.exceptions import ValidationError - - if not re.match(r"^[A-Za-z][A-Za-z0-9-_]+$", self.package_name): - raise ValidationError( - _( - "Plugin package_name (which equals to the main plugin folder inside the zip file) must start with an ASCII letter and can contain only ASCII letters, digits and the - and _ signs." - ) - ) - - if self.pk: - qs = Plugin.objects.filter(name__iexact=self.name).exclude(pk=self.pk) - else: - qs = Plugin.objects.filter(name__iexact=self.name) - if qs.count(): - raise ValidationError( - _( - "A plugin with a similar name (%s) already exists (the name only differs in case)." - ) - % qs.all()[0].name - ) - - if self.pk: - qs = Plugin.objects.filter(package_name__iexact=self.package_name).exclude( - pk=self.pk - ) - else: - qs = Plugin.objects.filter(package_name__iexact=self.package_name) - if qs.count(): - raise ValidationError( - _( - "A plugin with a similar package_name (%s) already exists (the package_name only differs in case)." - ) - % qs.all()[0].package_name - ) - - def save(self, keep_date=False, *args, **kwargs): - """ - Soft triggers: - * updates modified_on if keep_date is not set - * set maintainer to the plugin creator when not specified - """ - if self.pk and not keep_date: - import logging - - logging.debug("Updating modified_on for the Plugin instance") - self.modified_on = datetime.datetime.now() - if not self.pk: - self.modified_on = datetime.datetime.now() - if not self.maintainer: - self.maintainer = self.created_by - super(Plugin, self).save(*args, **kwargs) - - -# Plugin version managers - - -class ApprovedPluginVersions(models.Manager): - """ - Shows only public plugin versions: - """ - - def get_queryset(self): - return ( - super(ApprovedPluginVersions, self) - .get_queryset() - .filter(approved=True) - .order_by("-version") - ) - - -class StablePluginVersions(ApprovedPluginVersions): - """ - Shows only approved public plugin versions: i.e. those with "approved" flag set - and with "stable" flag - """ - - def get_queryset(self): - return ( - super(StablePluginVersions, self).get_queryset().filter(experimental=False) - ) - - -class ExperimentalPluginVersions(ApprovedPluginVersions): - """ - Shows only public plugin versions: i.e. those with "approved" flag set - and with "experimental" flag - """ - - def get_queryset(self): - return ( - super(ExperimentalPluginVersions, self) - .get_queryset() - .filter(experimental=True) - ) - - -def vjust(str, level=3, delim=".", bitsize=3, fillchar=" ", force_zero=False): - """ - Normalize a dotted version string. - - 1.12 becomes : 1. 12 - 1.1 becomes : 1. 1 - - - if force_zero=True and level=2: - - 1.12 becomes : 1. 12. 0 - 1.1 becomes : 1. 1. 0 - - - """ - if not str: - return str - nb = str.count(delim) - if nb < level: - if force_zero: - str += (level - nb) * (delim + "0") - else: - str += (level - nb) * delim - parts = [] - for v in str.split(delim)[: level + 1]: - if not v: - parts.append(v.rjust(bitsize, "#")) - else: - parts.append(v.rjust(bitsize, fillchar)) - return delim.join(parts) - - -class VersionField(models.CharField): - - description = 'Field to store version strings ("a.b.c.d") in a way it is sortable' - - def get_prep_value(self, value): - return vjust(value, fillchar="0") - - def to_python(self, value): - if not value: - return "" - return re.sub(VERSION_RE, "", value) - - def from_db_value(self, value, expression, connection): - if value is None: - return value - return self.to_python(value) - - -class QGVersionZeroForcedField(models.CharField): - - description = 'Field to store version strings ("a.b.c.d") in a way it \ - is sortable and QGIS scheme compatible (x.y.z).' - - def get_prep_value(self, value): - return vjust(value, fillchar="0", level=2, force_zero=True) - - def to_python(self, value): - if not value: - return "" - return re.sub(VERSION_RE, "", value) - - def from_db_value(self, value, expression, connection): - if value is None: - return value - return self.to_python(value) - - -class PluginOutstandingToken(models.Model): - """ - Plugin outstanding token - """ - plugin = models.ForeignKey( - Plugin, - on_delete=models.CASCADE - ) - token = models.ForeignKey( - OutstandingToken, - on_delete=models.CASCADE - ) - is_blacklisted = models.BooleanField(default=False) - is_newly_created = models.BooleanField(default=False) - description = models.CharField( - verbose_name=_("Description"), - help_text=_("Describe this token so that it's easier to remember where you're using it."), - max_length=512, - blank=True, - null=True, - ) - last_used_on = models.DateTimeField( - verbose_name=_("Last used on"), - blank=True, - null=True - ) - -class PluginVersion(models.Model): - """ - Plugin versions - """ - - # link to parent - plugin = models.ForeignKey(Plugin, on_delete=models.CASCADE) - # dates - created_on = models.DateTimeField( - _("Created on"), auto_now_add=True, editable=False - ) - # download counter - downloads = models.IntegerField(_("Downloads"), default=0, editable=False) - # owners - created_by = models.ForeignKey( - User, verbose_name=_("Created by"), on_delete=models.CASCADE, null=True, blank=True - ) - # version info, the first should be read from plugin - min_qg_version = QGVersionZeroForcedField( - _("Minimum QGIS version"), max_length=32, db_index=True - ) - max_qg_version = QGVersionZeroForcedField( - _("Maximum QGIS version"), max_length=32, null=True, blank=True, db_index=True - ) - version = VersionField(_("Version"), max_length=32, db_index=True) - changelog = models.TextField(_("Changelog"), null=True, blank=True) - - # the file! - package = models.FileField(_("Plugin package"), upload_to=PLUGINS_STORAGE_PATH) - # Flags: checks on unique current/experimental are done in save() and possibly in the views - experimental = models.BooleanField( - _("Experimental flag"), - default=False, - help_text=_( - "Check this box if this version is experimental, leave unchecked if it's stable. Please note that this field might be overridden by metadata (if present)." - ), - db_index=True, - ) - approved = models.BooleanField( - _("Approved"), - default=True, - help_text=_("Set to false if you wish to unapprove the plugin version."), - db_index=True, - ) - external_deps = models.CharField( - _("External dependencies"), - help_text=_("PIP install string"), - max_length=512, - blank=False, - null=True, - ) - is_from_token = models.BooleanField( - _("Is uploaded using token"), - default=False - ) - # Link to the token if upload is using token - token = models.ForeignKey( - PluginOutstandingToken, verbose_name=_("Token used"), on_delete=models.CASCADE, null=True, blank=True - ) - - # Managers, used in xml output - objects = models.Manager() - approved_objects = ApprovedPluginVersions() - stable_objects = StablePluginVersions() - experimental_objects = ExperimentalPluginVersions() - - @property - def file_name(self): - return os.path.basename(self.package.file.name) - - def save(self, *args, **kwargs): - """ - Soft triggers: - * updates modified_on in parent - """ - # Transforms the version... - # Need to be done here too, because clean() - # is only called in forms. - if self.version.rfind(" ") > 0: - self.version = self.version.rsplit(" ")[-1] - - # Only change modified_on when a new version is created, - # every download triggers a save to update the counter - if not self.pk: - self.plugin.modified_on = self.created_on - self.plugin.save() - - # fix Max version - if not self.max_qg_version: - self.max_qg_version = "%s.99" % tuple(self.min_qg_version.split(".")[0]) - - super(PluginVersion, self).save(*args, **kwargs) - - def clean(self): - """ - Validates: - - * checks for unique - * checks for version only digits and dots - """ - from django.core.exceptions import ValidationError - - # Transforms the version - self.version = PluginVersion.clean_version(self.version) - - versions_to_check = PluginVersion.objects.filter( - plugin=self.plugin, version=self.version - ) - if self.pk: - versions_to_check = versions_to_check.exclude(pk=self.pk) - # Checks for unique_together - if ( - versions_to_check.filter(plugin=self.plugin, version=self.version).count() - > 0 - ): - raise ValidationError( - _( - "Version value must be unique among each plugin: a version with same number already exists." - ) - ) - - @staticmethod - def clean_version(version): - """ - Strips blanks and Version string - """ - if version.rfind(" ") > 0: - version = version.rsplit(" ")[-1] - return version - - class Meta: - unique_together = ("plugin", "version") - ordering = ("plugin", "-version", "experimental") - - def get_absolute_url(self): - return reverse( - "version_detail", - args=( - self.plugin.package_name, - self.version, - ), - ) - - def get_download_url(self): - return reverse( - "version_download", - args=( - self.plugin.package_name, - self.version, - ), - ) - - def download_file_name(self): - return "%s.%s.zip" % (self.plugin.package_name, self.version) - - def __unicode__(self): - desc = "%s %s" % (self.plugin, self.version) - if self.experimental: - desc = "%s %s" % (desc, _("Experimental")) - return desc - - def __str__(self): - return self.__unicode__() - - -class PluginVersionFeedback(models.Model): - """Feedback for a plugin version.""" - - version = models.ForeignKey( - PluginVersion, - on_delete=models.CASCADE, - related_name="feedback" - ) - reviewer = models.ForeignKey( - User, - verbose_name=_("Reviewed by"), - help_text=_("The user who reviewed this plugin."), - on_delete=models.CASCADE, - ) - task = models.TextField( - verbose_name=_("Task"), - help_text=_("A feedback task. Please write your review as a task for this plugin."), - max_length=1000, - blank=False, - null=False - ) - created_on = models.DateTimeField( - verbose_name=_("Created on"), - auto_now_add=True, - editable=False - ) - modified_on = models.DateTimeField( - _("Modified on"), - editable=False, - blank=True, - null=True - ) - - completed_on = models.DateTimeField( - verbose_name=_("Completed on"), - blank=True, - null=True - ) - is_completed = models.BooleanField( - verbose_name=_("Completed"), - default=False, - db_index=True - ) - - class Meta: - ordering = ["created_on"] - - def save(self, *args, **kwargs): - if self.is_completed is True: - self.completed_on = datetime.datetime.now() - else: - self.completed_on = None - super(PluginVersionFeedback, self).save(*args, **kwargs) - - -def delete_version_package(sender, instance, **kw): - """ - Removes the zip package - """ - try: - os.remove(instance.package.path) - except: - pass - - -def delete_plugin_icon(sender, instance, **kw): - """ - Removes the plugin icon - """ - try: - os.remove(instance.icon.path) - except: - pass - - -class PluginVersionDownload(models.Model): - """ - Plugin version downloads - """ - plugin_version = models.ForeignKey( - PluginVersion, - on_delete=models.CASCADE - ) - download_date = models.DateField( - default=timezone.now - ) - country_code = models.CharField(max_length=3, default='N/D') - country_name = models.CharField(max_length=100, default='N/D') - download_count = models.IntegerField( - default=0 - ) - class Meta: - unique_together = ( - 'plugin_version', - 'download_date', - 'country_code', - 'country_name' - ) - - -models.signals.post_delete.connect(delete_version_package, sender=PluginVersion) -models.signals.post_delete.connect(delete_plugin_icon, sender=Plugin) diff --git a/qgis-app/plugins/search_indexes.py b/qgis-app/plugins/search_indexes.py deleted file mode 100644 index 889be9f1..00000000 --- a/qgis-app/plugins/search_indexes.py +++ /dev/null @@ -1,20 +0,0 @@ -from haystack import indexes -from plugins.models import Plugin - - -class PluginIndex(indexes.SearchIndex, indexes.Indexable): - text = indexes.CharField(document=True, use_template=True) - created_by = indexes.CharField(model_attr="created_by") - created_on = indexes.DateTimeField(model_attr="created_on") - # We add this for autocomplete. - name_auto = indexes.EdgeNgramField(model_attr="name") - description_auto = indexes.EdgeNgramField(model_attr="description") - about_auto = indexes.EdgeNgramField(model_attr="about", default="") - package_name_auto = indexes.EdgeNgramField(model_attr="package_name", default="") - - def get_model(self): - return Plugin - - def index_queryset(self, using=None): - """Only search in approved plugins.""" - return Plugin.approved_objects.all() diff --git a/qgis-app/plugins/tasks/__init__.py b/qgis-app/plugins/tasks/__init__.py deleted file mode 100644 index 4a4a1481..00000000 --- a/qgis-app/plugins/tasks/__init__.py +++ /dev/null @@ -1,4 +0,0 @@ -from plugins.tasks.generate_plugins_xml import * # noqa -from plugins.tasks.update_feedjack import * # noqa -from plugins.tasks.update_qgis_versions import * # noqa -from plugins.tasks.rebuild_search_index import * # noqa diff --git a/qgis-app/plugins/tasks/generate_plugins_xml.py b/qgis-app/plugins/tasks/generate_plugins_xml.py deleted file mode 100644 index a7415b60..00000000 --- a/qgis-app/plugins/tasks/generate_plugins_xml.py +++ /dev/null @@ -1,91 +0,0 @@ -import os - -import requests -from celery import shared_task -from celery.utils.log import get_task_logger -from preferences import preferences -from django.conf import settings -from preferences import preferences - - -logger = get_task_logger(__name__) - - -@shared_task -def generate_plugins_xml(site=""): - """ - Fetch the xml list of plugins from the plugin site. - :param site: site domain where the plugins will be fetched, default to - http://plugins.qgis.org - """ - logger.info('generate_plugins_xml : {}'.format(site)) - - if not site: - if settings.DEFAULT_PLUGINS_SITE: - site = settings.DEFAULT_PLUGINS_SITE - else: - site = "http://plugins.qgis.org" - plugins_url = "{}/plugins/plugins_new.xml".format(site) - - versions = preferences.SitePreference.qgis_versions - - if versions: - versions = versions.split(",") - else: - versions = [ - "1.8", - "2.0", - "2.2", - "2.4", - "2.6", - "2.8", - "2.10", - "2.12", - "2.14", - "2.15", - "2.16", - "2.17", - "2.18", - "2.99", - "3.0", - "3.1", - "3.2", - "3.3", - "3.4", - "3.5", - "3.6", - "3.7", - "3.8", - "3.9", - "3.10", - "3.11", - "3.12", - "3.13", - "3.14", - "3.15", - "3.16", - "3.17", - "3.18", - "3.19", - "3.20", - "3.21", - "3.22", - "3.23", - "3.24", - "3.25", - ] - - folder_path = os.path.join(settings.MEDIA_ROOT, "cached_xmls") - - if not os.path.exists(folder_path): - os.mkdir(folder_path) - - for version in versions: - response = requests.get( - "{url}?qgis={version}".format(url=plugins_url, version=version) - ) - - if response.status_code == 200: - file_name = "plugins_{}.xml".format(version) - with open(os.path.join(folder_path, file_name), "w+") as file: - file.write(response.text) diff --git a/qgis-app/plugins/tasks/rebuild_search_index.py b/qgis-app/plugins/tasks/rebuild_search_index.py deleted file mode 100644 index 50c76d4d..00000000 --- a/qgis-app/plugins/tasks/rebuild_search_index.py +++ /dev/null @@ -1,12 +0,0 @@ -from celery import shared_task -from haystack.management.commands import update_index -from celery.utils.log import get_task_logger - -logger = get_task_logger(__name__) - -@shared_task -def rebuild_search_index(): - """ - Celery task to rebuild the search index. - """ - update_index.Command().handle() \ No newline at end of file diff --git a/qgis-app/plugins/tasks/update_feedjack.py b/qgis-app/plugins/tasks/update_feedjack.py deleted file mode 100644 index e12d14bd..00000000 --- a/qgis-app/plugins/tasks/update_feedjack.py +++ /dev/null @@ -1,10 +0,0 @@ -from celery import shared_task -from celery.utils.log import get_task_logger - -logger = get_task_logger(__name__) - - -@shared_task -def update_feedjack(): - import subprocess - subprocess.call(['python', 'manage.py', 'feedjackupdate']) diff --git a/qgis-app/plugins/tasks/update_qgis_versions.py b/qgis-app/plugins/tasks/update_qgis_versions.py deleted file mode 100644 index 890d836c..00000000 --- a/qgis-app/plugins/tasks/update_qgis_versions.py +++ /dev/null @@ -1,27 +0,0 @@ -from celery import shared_task -from base.models.site_preferences import SitePreference -from plugins.utils import get_qgis_versions - - -@shared_task -def update_qgis_versions(): - """ - This background task fetches the QGIS versions from the GitHub QGIS releases - and then updates the current QGIS version in the database. - """ - site_preference = SitePreference.objects.first() - if not site_preference: - site_preference = SitePreference.objects.create() - - qgis_versions = get_qgis_versions() - stored_qgis_versions = site_preference.qgis_versions.split(',') - for qgis_version in qgis_versions: - if qgis_version not in stored_qgis_versions: - stored_qgis_versions.append(qgis_version) - stored_qgis_versions = list(filter(None, stored_qgis_versions)) - stored_qgis_versions = [tuple(map(int, v.split('.'))) for v in stored_qgis_versions] - stored_qgis_versions.sort(reverse=True) - stored_qgis_versions = ['.'.join(map(str, v)) for v in stored_qgis_versions] - - site_preference.qgis_versions = ','.join(stored_qgis_versions) - site_preference.save() diff --git a/qgis-app/plugins/templates/plugins/form_snippet.html b/qgis-app/plugins/templates/plugins/form_snippet.html deleted file mode 100755 index ae794508..00000000 --- a/qgis-app/plugins/templates/plugins/form_snippet.html +++ /dev/null @@ -1,27 +0,0 @@ -{% load i18n plugin_utils %} -
-{% for field in form %} -
- {% if field.field.widget|klass == 'CheckboxInput' %} - - {% else %} - {{ field.label_tag }} - {% if field.errors %} -
- {{ field.errors }} -
- {% endif %} - {{ field }} - {% endif %} -
{{ field.help_text }}
-
-{% endfor %} -
diff --git a/qgis-app/plugins/templates/plugins/pagination.html b/qgis-app/plugins/templates/plugins/pagination.html deleted file mode 100644 index 99bec111..00000000 --- a/qgis-app/plugins/templates/plugins/pagination.html +++ /dev/null @@ -1,50 +0,0 @@ -{% if is_paginated %} -{% load i18n %} - -{% endif %} diff --git a/qgis-app/plugins/templates/plugins/plugin_base.html b/qgis-app/plugins/templates/plugins/plugin_base.html deleted file mode 100644 index 31badff6..00000000 --- a/qgis-app/plugins/templates/plugins/plugin_base.html +++ /dev/null @@ -1,52 +0,0 @@ -{% extends BASE_TEMPLATE %}{% load i18n plugins_tagcloud static %} -{% load resources_custom_tags plugin_utils %} -{% block title %} - {% plugin_title %} — {% trans "QGIS Python Plugins Repository"%} -{% endblock %} - -{% block app_title %} -

{% trans "QGIS Python Plugins Repository"%}

-{% endblock %} - - -{% block menu %} -{{ block.super }} - {% trans "Upload a plugin" %} -

{% trans "Plugins" %}

- - -
-{% include_plugins_tagcloud_modal 'plugins.plugin' %} - -{% endblock %} - -{% block "credits" %} - {{ block.super }} -{% endblock %} diff --git a/qgis-app/plugins/templates/plugins/plugin_delete_confirm.html b/qgis-app/plugins/templates/plugins/plugin_delete_confirm.html deleted file mode 100644 index 4dbd8164..00000000 --- a/qgis-app/plugins/templates/plugins/plugin_delete_confirm.html +++ /dev/null @@ -1,9 +0,0 @@ -{% extends 'plugins/plugin_base.html' %}{% load i18n %} -{% block content %} -

{% trans "Delete plugin"%}: {{ plugin.title }}

-
{% csrf_token %} -

{% trans "You asked to delete the plugin and all its versions. The plugin will be permanently deleted. This action cannot be undone. Please confirm."%}

-

{% trans "Cancel" %}

-
- -{% endblock %} diff --git a/qgis-app/plugins/templates/plugins/plugin_detail.html b/qgis-app/plugins/templates/plugins/plugin_detail.html deleted file mode 100644 index 2448114e..00000000 --- a/qgis-app/plugins/templates/plugins/plugin_detail.html +++ /dev/null @@ -1,392 +0,0 @@ -{% extends 'plugins/plugin_base.html' %}{% load i18n static thumbnail %} -{% load local_timezone %} -{% load plugin_utils %} -{% block extrajs %} -{{ block.super }} - - - -{% endblock %} -{% block extracss %} - -{{ block.super }} - -{% endblock %} -{% block content %} -
- {% if object.stable or object.experimental %} - -
- {% else %} -
- {% endif %} -

{{ object.name }} - {% if object.icon and object.icon.file and object.icon|is_image_valid %} - {% with image_extension=object.icon.name|file_extension %} - {% if image_extension == 'svg' %} - {% trans - {% else %} - {% thumbnail object.icon "128x128" upscale=False format="PNG" as im %} - {% trans - {% endthumbnail %} - {% endif %} - {% endwith %} - {% else %} - {% trans - {% endif %} -

-
- {% trans "Plugin ID:" %} {{ object.pk }} -
- -
-
-
-
-
- {% if not object.experimental and not object.stable %} -
-

{% trans "This plugin has no public version yet." %}

-
- {% endif %} - - {% if not object.created_by.is_active %} -
-

{% trans "The plugin maintainer has been blocked." %}

-
- {% endif %} - - {% if object.deprecated %} -
-

{% trans "This plugin is deprecated!" %}

-
- {% endif %} - -
({% firstof votes '0' %}) {% trans "votes" %} 
-
-

{{ object.description|safe|linebreaksbr }}

-
- {% comment%} - {% if object.about %} -

{{ object.about|safe|linebreaksbr }}

- {% endif %} - {% endcomment %} - - - -
- {% if object.about %} -
-

{{ object.about|safe|linebreaksbr }}

-
- {% endif %} -
- {% if object.server %} -
{% trans "This plugin provides an interface for QGIS Server." %}
- {% endif %} -
- {% if object.author %} -
{% trans "Author"%}
-
- {{ object.author }} -
- {% endif %} - {% if object.email and not user.is_anonymous %} -
{% trans "Author's email"%}
-
{{ object.email }}
- {% endif %} - {% if object.display_created_by %} -
{% trans "Created by"%}
-
- {{ object.created_by }} -
- - {% endif %} -
{% trans "Maintainer"%}
-
- {{ object.maintainer }} -
- {% if object.owners.count %} -
{% trans "Collaborators"%}
-
- {% for owner in object.owners.all %} - {{ owner.username }}{% if not forloop.last %},{% endif %} - {% endfor %} -
- {% endif %} - {% if object.tags.count %} -
- {% trans "Tags"%} -
-
- {% for tag in object.tags.all %}{% if tag.slug %}{{tag}} - {% if not forloop.last %}, {% endif %}{% endif %}{% endfor %} -
- {% endif %} - {% if object.homepage %} -
{% trans "Plugin home page"%}
-
{{ object.homepage }}
- {% endif %} - {% if object.tracker %} -
{% trans "Tracker" %}
-
{% trans "Browse and report bugs" %}
- {% endif %} - {% if object.repository %} -
{% trans "Code repository" %}
-
{{ object.repository }}
- {% endif %} - {% if object.stable %} -
{% trans "Latest stable version"%}
-
{{ object.stable.version }}
- {% endif %} - {% if object.experimental %} -
{% trans "Latest experimental version"%}:
-
{{ object.experimental.version }}
- {% endif %} - {% if object.pk %} -
{% trans "Plugin ID"%}
-
- {{ object.pk }} -
- -
-
- {% endif %} -
-
-
- {% if object.pluginversion_set.count %} - {# show all versions if user is authorized #} -
- - - - - {% if not user.is_anonymous %}{% endif %} - - - - - - - {% if user.is_staff or user in object.approvers or user in object.editors %}{% endif %} - - - - {% for version in object.pluginversion_set.all %} - {% if version.approved or not user.is_anonymous %} - - - {% if not user.is_anonymous %}{% endif %} - - - - - {% if version.is_from_token %} - - {% else %} - - {% endif %} - - {% if user.is_staff or user in version.plugin.approvers or user in version.plugin.editors %} - {% endif %} - - {% endif %} - {% endfor %} - -
{% trans "Version" %}{% trans "Approved" %}{% trans "Experimental" %}{% trans "Min QGIS version" %}{% trans "Max QGIS version" %}{% trans "Downloads" %}{% trans "Uploaded by" %}{% trans "Date" %}{% trans "Manage" %}
{{ version.version }}{{ version.approved|yesno }}{{ version.experimental|yesno }}{{ version.min_qg_version }}{{ version.max_qg_version }}{{ version.downloads }}Token {{ version.token.description|default:"" }}{{ version.created_by }}{{ version.created_on|local_timezone }}
{% csrf_token %} - {% if user.is_staff or user in version.plugin.approvers %} - {% if not version.approved %} - - {% else %} - - {% endif %} - {% endif %} - - - {% if version.feedback|feedbacks_not_completed|length >= 2 %} - {{ version.feedback|feedbacks_not_completed|length }} - {% endif %} - - {% if user.is_staff or user in version.plugin.editors %} -  {% endif %}
-
-
- {% endif %} -
- - {% if user.is_staff or user in object.editors %} -
-
{% csrf_token %} -
- {% trans "Edit" %} - {% trans "Add version" %} - {% trans "Tokens" %} - {% if user.is_staff %} - {% if object.featured %} - {% else %} - {% endif %} - {% endif %} - {% if user.is_staff or user in object.editors %} - {% trans "Delete" %} - {% endif %} -
-
-
-
- -
- {% endif %} - {# end admin #} -
-
-{% endblock %} diff --git a/qgis-app/plugins/templates/plugins/plugin_feedback.html b/qgis-app/plugins/templates/plugins/plugin_feedback.html deleted file mode 100644 index 300e5ff3..00000000 --- a/qgis-app/plugins/templates/plugins/plugin_feedback.html +++ /dev/null @@ -1,235 +0,0 @@ -{% extends 'plugins/plugin_base.html' %}{% load i18n %} -{% block content %} - {% if form.errors %} -
- -

{% trans "The form contains errors and cannot be submitted, please check the fields highlighted in red." %}

-
- {% endif %} - {% if form.non_field_errors %} -
- - {% for error in form.non_field_errors %} -

{{ error }}

- {% endfor %} -
- {% endif %} -

{% trans "Feedback Plugin" %} {{ version.plugin.name }} {{ version.version }}

- - -{% endblock %} - -{% block extrajs %} - - - -{% endblock %} \ No newline at end of file diff --git a/qgis-app/plugins/templates/plugins/plugin_form.html b/qgis-app/plugins/templates/plugins/plugin_form.html deleted file mode 100644 index 86edcd27..00000000 --- a/qgis-app/plugins/templates/plugins/plugin_form.html +++ /dev/null @@ -1,108 +0,0 @@ -{% extends 'plugins/plugin_base.html' %}{% load static i18n %} -{% block extrajs %} -{{ block.super }} - - - - - - - - -{% endblock %} -{% block content %} -

{{ form_title }} {{ plugin }}

-
-

{% trans "required field." %}

-
- {% if form.errors %} -
- -

{% trans "The form contains errors and cannot be submitted, please check the fields highlighted in red." %}

-
- {% endif %} - {% if form.non_field_errors %} -
- - {% for error in form.non_field_errors %} -

{{ error }}

- {% endfor %} -
- {% endif %} -
{% csrf_token %} - {% include "plugins/form_snippet.html" %} -
- -
-
- - -{% endblock %} diff --git a/qgis-app/plugins/templates/plugins/plugin_list.html b/qgis-app/plugins/templates/plugins/plugin_list.html deleted file mode 100644 index c0457a35..00000000 --- a/qgis-app/plugins/templates/plugins/plugin_list.html +++ /dev/null @@ -1,183 +0,0 @@ -{% extends 'plugins/plugin_base.html' %}{% load i18n bootstrap_pagination humanize static sort_anchor range_filter thumbnail %} -{% load local_timezone %} -{% load plugin_utils %} -{% block extrajs %} - - -{% endblock %} -{% block content %} -

{% if title %}{{title}}{% else %}{% trans "All plugins" %}{% endif %}

- {# Filtered views menu #} - {% if object_list.count %} -
- {% blocktrans with records_count=page_obj.paginator.count %}{{ records_count }} records found{% endblocktrans %} —  - {% trans "Click to toggle descriptions." %} -
- - - - - - - {% if not user.is_anonymous %}{% endif %} - - - - - - - - - {% if user.is_authenticated %}{% endif %} - - - - {% for object in object_list %} - - - - {% if not user.is_anonymous %}{% endif %} - - - {% if object.author %} - - {% endif %} - - - - - - {% if user.is_authenticated %}{% if user in object.editors or user.is_staff %}{% endif %} - - - - - - {% endfor %} - -
 {% anchor name %}{% trans {% anchor featured %}{% anchor downloads %}{% anchor author "Author" %}{% anchor latest_version_date "Latest Plugin Version" %}{% anchor created_on "Created on" %}{% anchor average_vote "Stars (votes)" %}{% trans "Stable" %}{% trans "Exp." %}{% trans "Manage" %}
- {% if object.icon and object.icon.file and object.icon|is_image_valid %} - {% with image_extension=object.icon.name|file_extension %} - {% if image_extension == 'svg' %} - {% trans - {% else %} - {% thumbnail object.icon "24x24" format="PNG" as im %} - {% trans - {% endthumbnail %} - {% endif %} - {% endwith %} - {% else %} - {% trans - {% endif %} - {{ object.name }}{% if object.approved %}{% else %}—{% endif %}{% if object.featured%}{% else %}—{% endif %}{{ object.downloads }}{{ object.author }}{{ object.latest_version_date|local_timezone:"SHORT_NATURAL_DAY" }}{{ object.created_on|local_timezone:"SHORT" }}
({{ object.rating_votes }})
{% if object.stable %}{{ object.stable.version }}{% else %}—{% endif %}{% if object.experimental %}{{ object.experimental.version }}{% else %}—{% endif %} - {% else %}{% endif %}
- -
- - {% trans "Deprecated plugins are printed in red." %} -
- {% else %} - {% block plugins_message %} -
- - {% trans "This list is empty!" %} -
- {% endblock %} - {% endif %} - -{% endblock %} diff --git a/qgis-app/plugins/templates/plugins/plugin_list_my.html b/qgis-app/plugins/templates/plugins/plugin_list_my.html deleted file mode 100644 index 95fd2948..00000000 --- a/qgis-app/plugins/templates/plugins/plugin_list_my.html +++ /dev/null @@ -1,7 +0,0 @@ -{% extends 'plugins/plugin_list.html' %}{% load i18n %} - -{% block plugins_message %} - {% if not object_list.count %} -

{% trans "You have not uploaded any plugin yet:" %} {% trans "Create a new plugin" %}

- {% endif %} -{% endblock %} diff --git a/qgis-app/plugins/templates/plugins/plugin_permission_deny.html b/qgis-app/plugins/templates/plugins/plugin_permission_deny.html deleted file mode 100644 index 3602935f..00000000 --- a/qgis-app/plugins/templates/plugins/plugin_permission_deny.html +++ /dev/null @@ -1,4 +0,0 @@ -{% extends 'plugins/plugin_base.html' %}{% load i18n %} -{% block content %} -
{% trans "You cannot modify this plugin." %}
-{% endblock %} diff --git a/qgis-app/plugins/templates/plugins/plugin_token_delete_confirm.html b/qgis-app/plugins/templates/plugins/plugin_token_delete_confirm.html deleted file mode 100644 index 3cebe239..00000000 --- a/qgis-app/plugins/templates/plugins/plugin_token_delete_confirm.html +++ /dev/null @@ -1,9 +0,0 @@ -{% extends 'plugins/plugin_base.html' %}{% load i18n %} -{% block content %} -

Delete token of "{{ username }}"

-
{% csrf_token %} -

{% trans "You asked to delete a token.
The token will be permanently deleted and this action cannot be undone.
Please confirm." %}

-

{% trans "Cancel" %}

-
- -{% endblock %} diff --git a/qgis-app/plugins/templates/plugins/plugin_token_detail.html b/qgis-app/plugins/templates/plugins/plugin_token_detail.html deleted file mode 100644 index 1ab1101f..00000000 --- a/qgis-app/plugins/templates/plugins/plugin_token_detail.html +++ /dev/null @@ -1,114 +0,0 @@ -{% extends 'plugins/plugin_base.html' %}{% load i18n %} -{% load local_timezone %} -{% block content %} -

{% trans "Token for" %} {{ plugin.name }}

-
- - To enhance the security of the plugin token, - it will be displayed only once. Please ensure - to save it in a secure location. If the token - is lost, you can generate a new one at any time. -
-
-
{% trans "User"%}
-
- {{ object.user }} -
-
{% trans "Jti"%}
-
- {{object.jti}} -
-
{% trans "Created at"%}
-
- {{ object.created_at|local_timezone }} -
-
{% trans "Expires at"%}
-
- {{ object.expires_at|local_timezone }} -
-
{% trans "Access token"%}
-
- -
- -
- -
- -
- -{% endblock %} -{% block extracss %} -{{ block.super }} - -{% endblock %} - -{% block extrajs %} - -{% endblock %} \ No newline at end of file diff --git a/qgis-app/plugins/templates/plugins/plugin_token_form.html b/qgis-app/plugins/templates/plugins/plugin_token_form.html deleted file mode 100644 index c3a4908a..00000000 --- a/qgis-app/plugins/templates/plugins/plugin_token_form.html +++ /dev/null @@ -1,26 +0,0 @@ -{% extends 'plugins/plugin_base.html' %}{% load i18n %} -{% load local_timezone %} -{% block content %} -

{% trans "Edit token description " %} {{ token.jti }}

- -{% if form.errors %} -
- -

{% trans "The form contains errors and cannot be submitted, please check the fields highlighted in red." %}

-
-{% endif %} -{% if form.non_field_errors %} -
- - {% for error in form.non_field_errors %} -

{{ error }}

- {% endfor %} -
-{% endif %} -
{% csrf_token %} - {% include "plugins/form_snippet.html" %} -
- -
-
-{% endblock %} \ No newline at end of file diff --git a/qgis-app/plugins/templates/plugins/plugin_token_invalid_or_expired.html b/qgis-app/plugins/templates/plugins/plugin_token_invalid_or_expired.html deleted file mode 100644 index 3f6b286a..00000000 --- a/qgis-app/plugins/templates/plugins/plugin_token_invalid_or_expired.html +++ /dev/null @@ -1,4 +0,0 @@ -{% extends 'plugins/plugin_base.html' %}{% load i18n %} -{% block content %} -
{% trans "Token is invalid or expired." %}
-{% endblock %} diff --git a/qgis-app/plugins/templates/plugins/plugin_token_list.html b/qgis-app/plugins/templates/plugins/plugin_token_list.html deleted file mode 100644 index 530042b4..00000000 --- a/qgis-app/plugins/templates/plugins/plugin_token_list.html +++ /dev/null @@ -1,77 +0,0 @@ -{% extends 'plugins/plugin_base.html' %}{% load i18n %} -{% load local_timezone %} -{% block content %} -

{% trans "Tokens for" %} {{ plugin.name }}

-
{% csrf_token %} -
-

- -

-
-
-{% if object_list.count %} -
- - - - - - - - - - - - - {% for plugin_token in object_list %} - - - - - - - - - {% endfor %} - -
{% trans "User" %}{% trans "Description" %}{% trans "Jti" %}{% trans "Created at" %}{% trans "Last used at" %}{% trans "Manage" %}
{{ plugin_token.token.user }}{{ plugin_token.description|default:"-" }} - - {{ plugin_token.token.jti }} - - {{ plugin_token.token.created_at|local_timezone }}{{ plugin_token.last_used_on|default:"-"|local_timezone }} -   - - -
-
-{% else %} -
- - {% trans "This list is empty!" %} -
-{% endif %} - -{% endblock %} - -{% block extracss %} -{{ block.super }} - -{% endblock %} \ No newline at end of file diff --git a/qgis-app/plugins/templates/plugins/plugin_token_permission_deny.html b/qgis-app/plugins/templates/plugins/plugin_token_permission_deny.html deleted file mode 100644 index 7850ee82..00000000 --- a/qgis-app/plugins/templates/plugins/plugin_token_permission_deny.html +++ /dev/null @@ -1,4 +0,0 @@ -{% extends 'plugins/plugin_base.html' %}{% load i18n %} -{% block content %} -
{% trans "You cannot see tokens for this plugin." %}
-{% endblock %} diff --git a/qgis-app/plugins/templates/plugins/plugin_upload.html b/qgis-app/plugins/templates/plugins/plugin_upload.html deleted file mode 100644 index e2b0f67f..00000000 --- a/qgis-app/plugins/templates/plugins/plugin_upload.html +++ /dev/null @@ -1,45 +0,0 @@ -{% extends 'plugins/plugin_base.html' %}{% load i18n %} -{% block content %} -

{% trans "Upload a plugin" %}

-

{% trans "To upload a new plugin or update an existing one, you can specify the zipped file in this form." %}

-

{% trans "Alternatively, to update an existing plugin, you can also open the plugin's details view and add a new version from there." %}

- {% if form.non_field_errors %} -
- - {% for error in form.non_field_errors %} -

{{ error }}

- {% endfor %} -
- {% endif %} -
{% csrf_token %} - {% include "plugins/form_snippet.html" %} -
- - {% blocktrans %} - Please note that by uploading a plugin to the official QGIS plugin repository, - you agree that we will use your email to contact you. We will only contact - you for matters relating to the management of plugins and will not make - your email available to third parties for marketing purposes. - {% endblocktrans %} -
-
- - {% blocktrans %} - By uploading your plugin to the QGIS plugin repository, - you agree to keep your email address current and to be - responsive to any correspondence we may send you - regarding the management of your plugin. We require - this in order to be able to provide our users assurance - that the plugins we host are well maintained and will be - fixed should serious issues arise during their use. - We reserve the right to hide or remove plugins in cases - where the plugin author is not contactable and there - are issues reported about a plugin. - {% endblocktrans %} -
-
- -
-
- -{% endblock %} diff --git a/qgis-app/plugins/templates/plugins/plugins.xml b/qgis-app/plugins/templates/plugins/plugins.xml deleted file mode 100644 index cf28e89f..00000000 --- a/qgis-app/plugins/templates/plugins/plugins.xml +++ /dev/null @@ -1,30 +0,0 @@ -{% load static %}{% load local_timezone %} - - - {% for version in object_list %} - - {% if version.plugin.about %}{% endif %} - {{ version.version }} - {{ version.is_trusted }} - {{ version.min_qg_version }} - {{ version.max_qg_version }} - - {{ version.download_file_name }} - {% if version.plugin.icon %}{{ version.plugin.icon.url }}{% endif %} - - {% if request.is_secure %}https{% else %}http{% endif %}://{{ request.get_host }}{{ version.get_download_url }} - - {{ version.plugin.created_on|local_timezone:"WITH-UTC" }} - {{ version.created_on|local_timezone:"WITH-UTC" }} - {% if version.experimental %}True{% else%}False{% endif %} - {{ version.plugin.deprecated }} - - - - {{version.plugin.downloads}} - {{version.plugin.avg_vote}} - {{version.plugin.rating_votes}} - {{version.plugin.external_deps }} - {% if version.plugin.server %}True{% else%}False{% endif %} - {% endfor %} - diff --git a/qgis-app/plugins/templates/plugins/plugins_tagcloud_include.html b/qgis-app/plugins/templates/plugins/plugins_tagcloud_include.html deleted file mode 100644 index 6a9e7fd1..00000000 --- a/qgis-app/plugins/templates/plugins/plugins_tagcloud_include.html +++ /dev/null @@ -1,10 +0,0 @@ -{% load plugins_tagcloud %} - -{% get_plugins_tagcloud as tags %} - -
-{% for tag in tags %}{% if tag.slug %} -{{tag}} -{% endif %}{% endfor %} -
-
diff --git a/qgis-app/plugins/templates/plugins/plugins_tagcloud_modal_include.html b/qgis-app/plugins/templates/plugins/plugins_tagcloud_modal_include.html deleted file mode 100644 index 36898615..00000000 --- a/qgis-app/plugins/templates/plugins/plugins_tagcloud_modal_include.html +++ /dev/null @@ -1,21 +0,0 @@ -{% load i18n plugins_tagcloud %} - -{% trans "Plugin Tags" as tags_title %} - - - - {{ tags_title }} - - - - diff --git a/qgis-app/plugins/templates/plugins/tagcloud_include.html b/qgis-app/plugins/templates/plugins/tagcloud_include.html deleted file mode 100644 index 35b35bbd..00000000 --- a/qgis-app/plugins/templates/plugins/tagcloud_include.html +++ /dev/null @@ -1,14 +0,0 @@ -{% load plugins_tagcloud %} - -{% if forvar %} -{% get_tagcloud as tags for forvar %} -{% else %} -{% get_tagcloud as tags %} -{% endif %} - -
-{% for tag in tags %}{% if tag.slug %} -{{tag}} -{% endif %}{% endfor %} -
-
diff --git a/qgis-app/plugins/templates/plugins/user.html b/qgis-app/plugins/templates/plugins/user.html deleted file mode 100644 index 0c370502..00000000 --- a/qgis-app/plugins/templates/plugins/user.html +++ /dev/null @@ -1,29 +0,0 @@ -{% extends 'plugins/plugin_list.html' %}{% load i18n %} - -{% block content %} - - {% if user.is_staff %} -

{% trans "User Details of: " %} {{ plugin_user.username }}

-
    - {% if plugin_user.first_name %}
  • {% trans "First name: " %} {{ plugin_user.first_name }}
  • {% endif %} - {% if plugin_user.last_name %}
  • {% trans "Last name: " %} {{ plugin_user.last_name }}
  • {% endif %} - {% if plugin_user.email %} -
  • {% trans "Email: " %} {{ plugin_user.email }}
  • {% endif %} -
-
{% csrf_token %} -
- {% if plugin_user.is_active %} - {% else %} - - {% endif %} - {% if plugin_user.is_active %} - {% if not user_is_trusted %}{% else %} - {% endif %} - {% endif %} -
-
- {% endif %} - - {{ block.super }} - -{% endblock %} diff --git a/qgis-app/plugins/templates/plugins/version_delete_confirm.html b/qgis-app/plugins/templates/plugins/version_delete_confirm.html deleted file mode 100644 index 00c58b2a..00000000 --- a/qgis-app/plugins/templates/plugins/version_delete_confirm.html +++ /dev/null @@ -1,9 +0,0 @@ -{% extends 'plugins/plugin_base.html' %}{% load i18n %} -{% block content %} -

{% blocktrans with version.version as version and plugin.name as plugin_name %}Delete version "{{ version }}" of "{{ plugin_name }}"{% endblocktrans %}

-
{% csrf_token %} -

{% trans "You asked to delete one version.
The version will be permanently deleted and this action cannot be undone.
Please confirm." %}

-

{% trans "Cancel" %}

-
- -{% endblock %} diff --git a/qgis-app/plugins/templates/plugins/version_detail.html b/qgis-app/plugins/templates/plugins/version_detail.html deleted file mode 100644 index b9cd4874..00000000 --- a/qgis-app/plugins/templates/plugins/version_detail.html +++ /dev/null @@ -1,77 +0,0 @@ -{% extends 'plugins/plugin_base.html' %}{% load i18n %} -{% load local_timezone %} -{% block content %} -

{% trans "Version" %}: {{ version }}

- - - {% if not version.created_by.is_active and not version.is_from_token %} -
- {% trans "The plugin author has been blocked." %} -
- {% endif %} - - - - -
-
-
- {% if version.changelog %}
{% trans "Changelog" %}
{{ version.changelog|wordwrap:80 }}
{% endif %} -
{% trans "Approved" %}
{{ version.approved|yesno }}
-
{% trans "Author" %}
- {% if version.is_from_token %} - Token {{ version.token.description|default:"" }} - {% else %} - {{ version.created_by }} - {% endif %} -
-
{% trans "Uploaded" %}
{{ version.created_on|local_timezone }}
-
{% trans "Minimum QGIS version" %}
{{ version.min_qg_version }}
-
{% trans "Maximum QGIS version" %}
{{ version.max_qg_version }}
-
{% trans "External dependencies (PIP install string)" %}
{{ version.external_deps }}
-
{% trans "Experimental" %}
{{ version.experimental|yesno }}
-
-
- -
-

{% trans "Version management"%}

- {% if user.is_staff or user in version.plugin.editors %} - {% trans "Edit" %} - {% trans "Delete" %} - {% endif %} - {% trans "Plugin details" %} - - {% if user.is_staff or user in version.plugin.approvers %} -

{% trans "Version approval"%}

-
{% csrf_token %} - {% if not version.approved %} {% else %}{% endif %} -
- {% if user.is_staff %} -

{% trans "Author management"%}

-
{% csrf_token %} - {% if version.created_by.is_active %} - {% else %} - - {% endif %} - {% if version.created_by.is_active %} - {% if not version.plugin.trusted %}{% else %} - {% endif %} - {% endif %} -
- {% endif %} -
- {% endif %} -
- - - -{% endblock %} diff --git a/qgis-app/plugins/templates/plugins/version_form.html b/qgis-app/plugins/templates/plugins/version_form.html deleted file mode 100644 index b928d645..00000000 --- a/qgis-app/plugins/templates/plugins/version_form.html +++ /dev/null @@ -1,23 +0,0 @@ -{% extends 'plugins/plugin_base.html' %}{% load i18n %} -{% block content %} -

{{ form_title }} {{ version }}

-
-

{% trans "required field." %}

-
- {% if form.non_field_errors %} -
- - {% for error in form.non_field_errors %} -

{{ error }}

- {% endfor %} -
- {% endif %} - -
{% csrf_token %} - {% include "plugins/form_snippet.html" %} -
- -
-
- -{% endblock %} diff --git a/qgis-app/plugins/templates/plugins/version_permission_deny.html b/qgis-app/plugins/templates/plugins/version_permission_deny.html deleted file mode 100644 index 81e4c94b..00000000 --- a/qgis-app/plugins/templates/plugins/version_permission_deny.html +++ /dev/null @@ -1,4 +0,0 @@ -{% extends 'plugins/plugin_base.html' %}{% load i18n %} -{% block content %} -
{% trans "You cannot create or modify versions of this plugin." %}
-{% endblock %} diff --git a/qgis-app/plugins/templatetags/__init__.py b/qgis-app/plugins/templatetags/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/qgis-app/plugins/templatetags/local_timezone.py b/qgis-app/plugins/templatetags/local_timezone.py deleted file mode 100644 index 835e7c82..00000000 --- a/qgis-app/plugins/templatetags/local_timezone.py +++ /dev/null @@ -1,22 +0,0 @@ -import pytz -from django import template -from django.utils.safestring import mark_safe - -register = template.Library() - - -@register.filter(name="local_timezone", is_safe=True) -def local_timezone(date, args="LONG"): - try: - utcdate = date.astimezone(pytz.utc).isoformat() - if args and str(args) == "SHORT": - result = '%s' % (utcdate,) - elif args and str(args) == "SHORT_NATURAL_DAY": - result = '%s' % (utcdate,) - elif args and str(args) == "WITH-UTC": - result = utcdate - else: - result = '%s' % (utcdate,) - except AttributeError: - result = date - return mark_safe(result) diff --git a/qgis-app/plugins/templatetags/plugin_utils.py b/qgis-app/plugins/templatetags/plugin_utils.py deleted file mode 100755 index 3daf1722..00000000 --- a/qgis-app/plugins/templatetags/plugin_utils.py +++ /dev/null @@ -1,62 +0,0 @@ -from django import template -from PIL import Image, UnidentifiedImageError -import xml.etree.ElementTree as ET - -register = template.Library() - - -@register.filter("klass") -def klass(ob): - return ob.__class__.__name__ - - -@register.simple_tag(takes_context=True) -def plugin_title(context): - """Returns plugin name for title""" - title = "" - - if "title" in context: - title = context["title"] - if "plugin" in context: - title = context["plugin"].name - if "version" in context: - title = "{plugin} {version}".format( - plugin=context["version"].plugin.name, version=context["version"].version - ) - if "page_title" in context: - title = context["page_title"] - return title - -@register.filter -def file_extension(value): - return value.split('.')[-1].lower() - -@register.filter -def is_image_valid(image): - if not image: - return False - # Check if the file is an SVG by extension - if image.path.lower().endswith('.svg'): - return _validate_svg(image.path) - return _validate_image(image.path) - - -def _validate_svg(file_path): - try: - # Parse the SVG file to ensure it's well-formed XML - ET.parse(file_path) - return True - except (ET.ParseError, FileNotFoundError): - return False - -def _validate_image(file_path): - try: - img = Image.open(file_path) - img.verify() - return True - except (FileNotFoundError, UnidentifiedImageError): - return False - -@register.filter -def feedbacks_not_completed(feedbacks): - return feedbacks.filter(is_completed=False) diff --git a/qgis-app/plugins/templatetags/plugins_tagcloud.py b/qgis-app/plugins/templatetags/plugins_tagcloud.py deleted file mode 100644 index 6a070c6a..00000000 --- a/qgis-app/plugins/templatetags/plugins_tagcloud.py +++ /dev/null @@ -1,106 +0,0 @@ -""" -ABP: patched version of django-taggit-templatetags to deal with -unpublished plugins: returns only approved_objects - -""" - -from django import template -from django.conf import settings as django_settings -from django.core.exceptions import FieldError -from django.db import models -from django.db.models import Count -from plugins.models import Plugin -from taggit import VERSION as TAGGIT_VERSION -from taggit.managers import TaggableManager -from taggit.models import Tag, TaggedItem -from taggit_templatetags import settings -from templatetag_sugar.parser import Constant, Model, Name, Optional, Variable -from templatetag_sugar.register import tag - -TAGCLOUD_COUNT_GTE = getattr(django_settings, "TAGCLOUD_COUNT_GTE", None) -T_MAX = getattr(settings, "TAGCLOUD_MAX", 6.0) -T_MIN = getattr(settings, "TAGCLOUD_MIN", 1.0) - -register = template.Library() - - -def get_queryset(): - applabel = "plugins" - model = "plugin" - # filter tagged items - queryset = TaggedItem.objects.filter(content_type__app_label=applabel.lower()) - queryset = queryset.filter( - content_type__model=model.lower(), - object_id__in=Plugin.approved_objects.values_list("id", flat=True), - ) - - # get tags - tag_ids = queryset.values_list("tag_id", flat=True) - queryset = Tag.objects.filter(id__in=tag_ids) - - # Retain compatibility with older versions of Django taggit - # a version check (for example taggit.VERSION <= (0,8,0)) does NOT - # work because of the version (0,8,0) of the current dev version of django-taggit - try: - qs = queryset.annotate(num_times=Count("taggeditem_items")) - except FieldError: - qs = queryset.annotate(num_times=Count("taggit_taggeditem_items")) - if TAGCLOUD_COUNT_GTE: - qs = qs.filter(num_times__gte=TAGCLOUD_COUNT_GTE) - return qs - - -def get_weight_fun(t_min, t_max, f_min, f_max): - def weight_fun(f_i, t_min=t_min, t_max=t_max, f_min=f_min, f_max=f_max): - # Prevent a division by zero here, found to occur under some - # pathological but nevertheless actually occurring circumstances. - if f_max == f_min: - mult_fac = 1.0 - else: - mult_fac = float(t_max - t_min) / float(f_max - f_min) - - return t_max - (f_max - f_i) * mult_fac - - return weight_fun - - -@tag(register, [Constant("as"), Name()]) -def get_plugins_taglist(context, asvar): - queryset = get_queryset() - queryset = queryset.order_by("-num_times") - context[asvar] = queryset - return "" - - -@tag(register, [Constant("as"), Name()]) -def get_plugins_tagcloud(context, asvar): - queryset = get_queryset() - num_times = queryset.values_list("num_times", flat=True) - if len(num_times) == 0: - context[asvar] = queryset - return "" - weight_fun = get_weight_fun(T_MIN, T_MAX, min(num_times), max(num_times)) - queryset = queryset.order_by("name") - for tag in queryset: - tag.weight = weight_fun(tag.num_times) - context[asvar] = queryset - return "" - - -def include_plugins_tagcloud(forvar=None): - pass - -def include_plugins_tagcloud_modal(forvar=None): - pass - -def include_plugins_taglist(forvar=None): - pass - - -register.inclusion_tag("plugins/plugins_taglist_include.html")(include_plugins_taglist) -register.inclusion_tag("plugins/plugins_tagcloud_include.html")( - include_plugins_tagcloud -) -register.inclusion_tag("plugins/plugins_tagcloud_modal_include.html")( - include_plugins_tagcloud_modal -) diff --git a/qgis-app/plugins/templatetags/range_filter.py b/qgis-app/plugins/templatetags/range_filter.py deleted file mode 100755 index c1090b59..00000000 --- a/qgis-app/plugins/templatetags/range_filter.py +++ /dev/null @@ -1,27 +0,0 @@ -from django.template import Library - -register = Library() - - -@register.filter -def get_range(value): - """ - Filter - returns a list containing range made from given value - Usage (in template): - -
    {% for i in 3|get_range %} -
  • {{ i }}. Do something
  • - {% endfor %}
- - Results with the HTML: -
    -
  • 0. Do something
  • -
  • 1. Do something
  • -
  • 2. Do something
  • -
- - Instead of 3 one may use the variable set in the views - """ - if not value: - value = 0 - return range(value) diff --git a/qgis-app/plugins/templatetags/smart_paginate.py b/qgis-app/plugins/templatetags/smart_paginate.py deleted file mode 100644 index 9c706a40..00000000 --- a/qgis-app/plugins/templatetags/smart_paginate.py +++ /dev/null @@ -1,257 +0,0 @@ -# Smmarter paginator -# Fix an error in try/except block and adds per_page - - -try: - set -except NameError: - from sets import Set as set - -from django import template -from django.conf import settings -from django.core.paginator import InvalidPage, Paginator -from django.http import Http404 - -register = template.Library() - -DEFAULT_PAGINATION = getattr(settings, "PAGINATION_DEFAULT_PAGINATION", 20) -DEFAULT_WINDOW = getattr(settings, "PAGINATION_DEFAULT_WINDOW", 4) -DEFAULT_ORPHANS = getattr(settings, "PAGINATION_DEFAULT_ORPHANS", 0) -INVALID_PAGE_RAISES_404 = getattr(settings, "PAGINATION_INVALID_PAGE_RAISES_404", False) - - -def do_autopaginate(parser, token): - """ - Splits the arguments to the autopaginate tag and formats them correctly. - """ - split = token.split_contents() - as_index = None - context_var = None - for i, bit in enumerate(split): - if bit == "as": - as_index = i - break - if as_index is not None: - try: - context_var = split[as_index + 1] - except IndexError: - raise template.TemplateSyntaxError( - "Context variable assignment " - + "must take the form of {%% %r object.example_set.all ... as " - + "context_var_name %%}" % split[0] - ) - del split[as_index : as_index + 2] - if len(split) == 2: - return AutoPaginateNode(split[1]) - elif len(split) == 3: - return AutoPaginateNode(split[1], paginate_by=split[2], context_var=context_var) - elif len(split) == 4: - try: - orphans = int(split[3]) - except ValueError: - raise template.TemplateSyntaxError( - u"Got %s, but expected integer." % split[3] - ) - return AutoPaginateNode( - split[1], paginate_by=split[2], orphans=orphans, context_var=context_var - ) - else: - raise template.TemplateSyntaxError( - "%r tag takes one required " - + "argument and one optional argument" % split[0] - ) - - -class AutoPaginateNode(template.Node): - """ - Emits the required objects to allow for Digg-style pagination. - - First, it looks in the current context for the variable specified, and using - that object, it emits a simple ``Paginator`` and the current page object - into the context names ``paginator`` and ``page_obj``, respectively. - - It will then replace the variable specified with only the objects for the - current page. - - .. note:: - - It is recommended to use *{% paginate %}* after using the autopaginate - tag. If you choose not to use *{% paginate %}*, make sure to display the - list of available pages, or else the application may seem to be buggy. - """ - - def __init__( - self, - queryset_var, - paginate_by=DEFAULT_PAGINATION, - orphans=DEFAULT_ORPHANS, - context_var=None, - ): - self.queryset_var = template.Variable(queryset_var) - if isinstance(paginate_by, int): - self.paginate_by = paginate_by - else: - self.paginate_by = template.Variable(paginate_by) - self.orphans = orphans - self.context_var = context_var - - def render(self, context): - key = self.queryset_var.var - value = self.queryset_var.resolve(context) - if isinstance(self.paginate_by, int): - paginate_by = self.paginate_by - else: - paginate_by = self.paginate_by.resolve(context) - paginator = Paginator(value, paginate_by, self.orphans) - try: - page_obj = paginator.page(context["request"].page) - except InvalidPage: - if INVALID_PAGE_RAISES_404: - raise Http404( - "Invalid page requested. If DEBUG were set to " - + "False, an HTTP 404 page would have been shown instead." - ) - context[key] = [] - context["invalid_page"] = True - return u"" - if self.context_var is not None: - context[self.context_var] = page_obj.object_list - else: - context[key] = page_obj.object_list - context["paginator"] = paginator - context["page_obj"] = page_obj - return u"" - - -def smart_paginate(context, window=DEFAULT_WINDOW, hashtag=""): - """ - Renders the ``pagination/pagination.html`` template, resulting in a - Digg-like display of the available pages, given the current page. If there - are too many pages to be displayed before and after the current page, then - elipses will be used to indicate the undisplayed gap between page numbers. - - Requires one argument, ``context``, which should be a dictionary-like data - structure and must contain the following keys: - - ``paginator`` - A ``Paginator`` or ``QuerySetPaginator`` object. - - ``page_obj`` - This should be the result of calling the page method on the - aforementioned ``Paginator`` or ``QuerySetPaginator`` object, given - the current page. - - This same ``context`` dictionary-like data structure may also include: - - ``getvars`` - A dictionary of all of the **GET** parameters in the current request. - This is useful to maintain certain types of state, even when requesting - a different page. - """ - try: - paginator = context["paginator"] - page_obj = context["page_obj"] - page_range = paginator.page_range - # Calculate the record range in the current page for display. - records = {"first": 1 + (page_obj.number - 1) * paginator.per_page} - records["last"] = records["first"] + paginator.per_page - 1 - if records["last"] + paginator.orphans >= paginator.count: - records["last"] = paginator.count - # First and last are simply the first *n* pages and the last *n* pages, - # where *n* is the current window size. - first = set(page_range[:window]) - last = set(page_range[-window:]) - # Now we look around our current page, making sure that we don't wrap - # around. - current_start = page_obj.number - 1 - window - if current_start < 0: - current_start = 0 - current_end = page_obj.number - 1 + window - if current_end < 0: - current_end = 0 - current = set(page_range[current_start:current_end]) - pages = [] - # If there's no overlap between the first set of pages and the current - # set of pages, then there's a possible need for elusion. - if len(first.intersection(current)) == 0: - first_list = list(first) - first_list.sort() - second_list = list(current) - second_list.sort() - pages.extend(first_list) - diff = second_list[0] - first_list[-1] - # If there is a gap of two, between the last page of the first - # set and the first page of the current set, then we're missing a - # page. - if diff == 2: - pages.append(second_list[0] - 1) - # If the difference is just one, then there's nothing to be done, - # as the pages need no elusion and are correct. - elif diff == 1: - pass - # Otherwise, there's a bigger gap which needs to be signaled for - # elusion, by pushing a None value to the page list. - else: - pages.append(None) - pages.extend(second_list) - else: - unioned = list(first.union(current)) - unioned.sort() - pages.extend(unioned) - # If there's no overlap between the current set of pages and the last - # set of pages, then there's a possible need for elusion. - if len(current.intersection(last)) == 0: - second_list = list(last) - second_list.sort() - diff = second_list[0] - pages[-1] - # If there is a gap of two, between the last page of the current - # set and the first page of the last set, then we're missing a - # page. - if diff == 2: - pages.append(second_list[0] - 1) - # If the difference is just one, then there's nothing to be done, - # as the pages need no elusion and are correct. - elif diff == 1: - pass - # Otherwise, there's a bigger gap which needs to be signaled for - # elusion, by pushing a None value to the page list. - else: - pages.append(None) - pages.extend(second_list) - else: - differenced = list(last.difference(current)) - differenced.sort() - pages.extend(differenced) - - # ABP: per_page - if paginator.count > DEFAULT_PAGINATION: - per_page_list = range(0, paginator.count + 1, DEFAULT_PAGINATION) - per_page_list = per_page_list[1:] - per_page_list.append(paginator.count) - else: - per_page_list = [] - to_return = { - "MEDIA_URL": settings.MEDIA_URL, - "pages": pages, - "records": records, - "page_obj": page_obj, - "per_page_list": per_page_list, - "paginator": paginator, - "hashtag": hashtag, - "is_paginated": paginator.count > paginator.per_page, - } - if "request" in context: - getvars = context["request"].GET.copy() - if "page" in getvars: - del getvars["page"] - if len(getvars.keys()) > 0: - to_return["getvars"] = "&%s" % getvars.urlencode() - else: - to_return["getvars"] = "" - return to_return - # ABP: turned exceptions into a tuple - except (KeyError, AttributeError): - return {} - - -register.inclusion_tag("plugins/pagination.html", takes_context=True)(smart_paginate) diff --git a/qgis-app/plugins/tests/HelloWorld/1.0-spaced/Hello World/HelloWorld.py b/qgis-app/plugins/tests/HelloWorld/1.0-spaced/Hello World/HelloWorld.py deleted file mode 100644 index 45bdac04..00000000 --- a/qgis-app/plugins/tests/HelloWorld/1.0-spaced/Hello World/HelloWorld.py +++ /dev/null @@ -1,40 +0,0 @@ -# -*- coding: utf-8 -*- -# Import the PyQt and QGIS libraries -from PyQt4.QtCore import * -from PyQt4.QtGui import * -from qgis.core import * - - -class HelloWorld: - def __init__(self, iface): - # Save reference to the QGIS interface - self.iface = iface - self.canvas = iface.mapCanvas() - - def initGui(self): - # Create action that will start plugin - self.action = QAction( - QIcon(":/plugins/"), "&HelloWorld", self.iface.mainWindow() - ) - # connect the action to the run method - QObject.connect(self.action, SIGNAL("activated()"), self.hello_world) - - # Add toolbar button and menu item - self.iface.addPluginToMenu("HelloWorld", self.action) - - def unload(self): - # Remove the plugin menu item and icon - self.iface.removePluginMenu("HelloWorld", self.action) - - # run - def hello_world(self): - QMessageBox.information( - self.iface.mainWindow(), - QCoreApplication.translate("HelloWorld", "HelloWorld"), - QCoreApplication.translate("HelloWorld", "HelloWorld"), - ) - return - - -if __name__ == "__main__": - pass diff --git a/qgis-app/plugins/tests/HelloWorld/1.0-spaced/Hello World/__init__.py b/qgis-app/plugins/tests/HelloWorld/1.0-spaced/Hello World/__init__.py deleted file mode 100644 index 0e3e06b7..00000000 --- a/qgis-app/plugins/tests/HelloWorld/1.0-spaced/Hello World/__init__.py +++ /dev/null @@ -1,33 +0,0 @@ -# -*- coding: utf-8 -*- -""" - This script initializes the plugin, making it known to QGIS. -""" - - -def name(): - return "Hello World" - - -def description(): - return "HelloWorld" - - -def version(): - return "Version 1.0" - - -def qgisMinimumVersion(): - return "1.0" - - -def classFactory(iface): - from HelloWorld import HelloWorld - - return HelloWorld(iface) - - -def icon(): - """ - Icon - """ - return "icon.png" diff --git a/qgis-app/plugins/tests/HelloWorld/1.0-spaced/Hello World/icon.png b/qgis-app/plugins/tests/HelloWorld/1.0-spaced/Hello World/icon.png deleted file mode 100644 index c043d921..00000000 Binary files a/qgis-app/plugins/tests/HelloWorld/1.0-spaced/Hello World/icon.png and /dev/null differ diff --git a/qgis-app/plugins/tests/HelloWorld/1.0/HelloWorld/HelloWorld.py b/qgis-app/plugins/tests/HelloWorld/1.0/HelloWorld/HelloWorld.py deleted file mode 100644 index 45bdac04..00000000 --- a/qgis-app/plugins/tests/HelloWorld/1.0/HelloWorld/HelloWorld.py +++ /dev/null @@ -1,40 +0,0 @@ -# -*- coding: utf-8 -*- -# Import the PyQt and QGIS libraries -from PyQt4.QtCore import * -from PyQt4.QtGui import * -from qgis.core import * - - -class HelloWorld: - def __init__(self, iface): - # Save reference to the QGIS interface - self.iface = iface - self.canvas = iface.mapCanvas() - - def initGui(self): - # Create action that will start plugin - self.action = QAction( - QIcon(":/plugins/"), "&HelloWorld", self.iface.mainWindow() - ) - # connect the action to the run method - QObject.connect(self.action, SIGNAL("activated()"), self.hello_world) - - # Add toolbar button and menu item - self.iface.addPluginToMenu("HelloWorld", self.action) - - def unload(self): - # Remove the plugin menu item and icon - self.iface.removePluginMenu("HelloWorld", self.action) - - # run - def hello_world(self): - QMessageBox.information( - self.iface.mainWindow(), - QCoreApplication.translate("HelloWorld", "HelloWorld"), - QCoreApplication.translate("HelloWorld", "HelloWorld"), - ) - return - - -if __name__ == "__main__": - pass diff --git a/qgis-app/plugins/tests/HelloWorld/1.0/HelloWorld/__init__.py b/qgis-app/plugins/tests/HelloWorld/1.0/HelloWorld/__init__.py deleted file mode 100644 index 452d0afd..00000000 --- a/qgis-app/plugins/tests/HelloWorld/1.0/HelloWorld/__init__.py +++ /dev/null @@ -1,33 +0,0 @@ -# -*- coding: utf-8 -*- -""" - This script initializes the plugin, making it known to QGIS. -""" - - -def name(): - return "HelloWorld" - - -def description(): - return "HelloWorld" - - -def version(): - return "Version 1.0" - - -def qgisMinimumVersion(): - return "1.0" - - -def classFactory(iface): - from HelloWorld import HelloWorld - - return HelloWorld(iface) - - -def icon(): - """ - Icon - """ - return "icon.png" diff --git a/qgis-app/plugins/tests/HelloWorld/1.0/HelloWorld/icon.png b/qgis-app/plugins/tests/HelloWorld/1.0/HelloWorld/icon.png deleted file mode 100644 index c043d921..00000000 Binary files a/qgis-app/plugins/tests/HelloWorld/1.0/HelloWorld/icon.png and /dev/null differ diff --git a/qgis-app/plugins/tests/HelloWorld/1.1/HelloWorld/HelloWorld.py b/qgis-app/plugins/tests/HelloWorld/1.1/HelloWorld/HelloWorld.py deleted file mode 100644 index 45bdac04..00000000 --- a/qgis-app/plugins/tests/HelloWorld/1.1/HelloWorld/HelloWorld.py +++ /dev/null @@ -1,40 +0,0 @@ -# -*- coding: utf-8 -*- -# Import the PyQt and QGIS libraries -from PyQt4.QtCore import * -from PyQt4.QtGui import * -from qgis.core import * - - -class HelloWorld: - def __init__(self, iface): - # Save reference to the QGIS interface - self.iface = iface - self.canvas = iface.mapCanvas() - - def initGui(self): - # Create action that will start plugin - self.action = QAction( - QIcon(":/plugins/"), "&HelloWorld", self.iface.mainWindow() - ) - # connect the action to the run method - QObject.connect(self.action, SIGNAL("activated()"), self.hello_world) - - # Add toolbar button and menu item - self.iface.addPluginToMenu("HelloWorld", self.action) - - def unload(self): - # Remove the plugin menu item and icon - self.iface.removePluginMenu("HelloWorld", self.action) - - # run - def hello_world(self): - QMessageBox.information( - self.iface.mainWindow(), - QCoreApplication.translate("HelloWorld", "HelloWorld"), - QCoreApplication.translate("HelloWorld", "HelloWorld"), - ) - return - - -if __name__ == "__main__": - pass diff --git a/qgis-app/plugins/tests/HelloWorld/1.1/HelloWorld/__init__.py b/qgis-app/plugins/tests/HelloWorld/1.1/HelloWorld/__init__.py deleted file mode 100644 index 5f7dc3df..00000000 --- a/qgis-app/plugins/tests/HelloWorld/1.1/HelloWorld/__init__.py +++ /dev/null @@ -1,33 +0,0 @@ -# -*- coding: utf-8 -*- -""" - This script initializes the plugin, making it known to QGIS. -""" - - -def name(): - return "HelloWorld" - - -def description(): - return "HelloWorld" - - -def version(): - return "Version 1.1" - - -def qgisMinimumVersion(): - return "1.0" - - -def classFactory(iface): - from HelloWorld import HelloWorld - - return HelloWorld(iface) - - -def icon(): - """ - Icon - """ - return "icon.png" diff --git a/qgis-app/plugins/tests/HelloWorld/1.1/HelloWorld/icon.png b/qgis-app/plugins/tests/HelloWorld/1.1/HelloWorld/icon.png deleted file mode 100644 index c043d921..00000000 Binary files a/qgis-app/plugins/tests/HelloWorld/1.1/HelloWorld/icon.png and /dev/null differ diff --git a/qgis-app/plugins/tests/HelloWorld/1.2-md-full/HelloWorld/HelloWorld.py b/qgis-app/plugins/tests/HelloWorld/1.2-md-full/HelloWorld/HelloWorld.py deleted file mode 100644 index 45bdac04..00000000 --- a/qgis-app/plugins/tests/HelloWorld/1.2-md-full/HelloWorld/HelloWorld.py +++ /dev/null @@ -1,40 +0,0 @@ -# -*- coding: utf-8 -*- -# Import the PyQt and QGIS libraries -from PyQt4.QtCore import * -from PyQt4.QtGui import * -from qgis.core import * - - -class HelloWorld: - def __init__(self, iface): - # Save reference to the QGIS interface - self.iface = iface - self.canvas = iface.mapCanvas() - - def initGui(self): - # Create action that will start plugin - self.action = QAction( - QIcon(":/plugins/"), "&HelloWorld", self.iface.mainWindow() - ) - # connect the action to the run method - QObject.connect(self.action, SIGNAL("activated()"), self.hello_world) - - # Add toolbar button and menu item - self.iface.addPluginToMenu("HelloWorld", self.action) - - def unload(self): - # Remove the plugin menu item and icon - self.iface.removePluginMenu("HelloWorld", self.action) - - # run - def hello_world(self): - QMessageBox.information( - self.iface.mainWindow(), - QCoreApplication.translate("HelloWorld", "HelloWorld"), - QCoreApplication.translate("HelloWorld", "HelloWorld"), - ) - return - - -if __name__ == "__main__": - pass diff --git a/qgis-app/plugins/tests/HelloWorld/1.2-md-full/HelloWorld/__init__.py b/qgis-app/plugins/tests/HelloWorld/1.2-md-full/HelloWorld/__init__.py deleted file mode 100644 index 58656605..00000000 --- a/qgis-app/plugins/tests/HelloWorld/1.2-md-full/HelloWorld/__init__.py +++ /dev/null @@ -1,49 +0,0 @@ -# -*- coding: utf-8 -*- -""" - This script initializes the plugin, making it known to QGIS. -""" - - -def name(): - return "HelloWorld" - - -def description(): - return "HelloWorld" - - -def version(): - return "Version 1.1" - - -def qgisMinimumVersion(): - return "1.0" - - -def classFactory(iface): - from HelloWorld import HelloWorld - - return HelloWorld(iface) - - -def icon(): - """ - Icon - """ - return "icon.png" - - -def deprecated(): - return True - - -def experimental(): - return True - - -def author(): - return "Alessandro Secondo" - - -def email(): - return "email2@email.com" diff --git a/qgis-app/plugins/tests/HelloWorld/1.2-md-full/HelloWorld/icon.png b/qgis-app/plugins/tests/HelloWorld/1.2-md-full/HelloWorld/icon.png deleted file mode 100644 index c043d921..00000000 Binary files a/qgis-app/plugins/tests/HelloWorld/1.2-md-full/HelloWorld/icon.png and /dev/null differ diff --git a/qgis-app/plugins/tests/HelloWorld/1.2-md-full/HelloWorld/metadata.txt b/qgis-app/plugins/tests/HelloWorld/1.2-md-full/HelloWorld/metadata.txt deleted file mode 100644 index 65be10da..00000000 --- a/qgis-app/plugins/tests/HelloWorld/1.2-md-full/HelloWorld/metadata.txt +++ /dev/null @@ -1,29 +0,0 @@ -[general] -name=HelloWorld -qgisMinimumVersion=1.6 -description=This is a plugin for greeting the - (going multiline) world -version=version 1.2 -author=Alessandro Primo -email=email@email.com -about=An Hello World Plugin: nothing more - -changelog=this is a very - very
- very - very - very - very long multiline changelog - - -tags=wkt,raster,hello world - -tracker=http://bugs.itopen.it -homepage=http://www.itopen.it -repository=http://www.itopen.it/repo - -icon=icon.png - -experimental=True -; test numeric value flag -deprecated=1 diff --git a/qgis-app/plugins/tests/HelloWorld/1.2-md-txt-incomplete/HelloWorld/HelloWorld.py b/qgis-app/plugins/tests/HelloWorld/1.2-md-txt-incomplete/HelloWorld/HelloWorld.py deleted file mode 100644 index 45bdac04..00000000 --- a/qgis-app/plugins/tests/HelloWorld/1.2-md-txt-incomplete/HelloWorld/HelloWorld.py +++ /dev/null @@ -1,40 +0,0 @@ -# -*- coding: utf-8 -*- -# Import the PyQt and QGIS libraries -from PyQt4.QtCore import * -from PyQt4.QtGui import * -from qgis.core import * - - -class HelloWorld: - def __init__(self, iface): - # Save reference to the QGIS interface - self.iface = iface - self.canvas = iface.mapCanvas() - - def initGui(self): - # Create action that will start plugin - self.action = QAction( - QIcon(":/plugins/"), "&HelloWorld", self.iface.mainWindow() - ) - # connect the action to the run method - QObject.connect(self.action, SIGNAL("activated()"), self.hello_world) - - # Add toolbar button and menu item - self.iface.addPluginToMenu("HelloWorld", self.action) - - def unload(self): - # Remove the plugin menu item and icon - self.iface.removePluginMenu("HelloWorld", self.action) - - # run - def hello_world(self): - QMessageBox.information( - self.iface.mainWindow(), - QCoreApplication.translate("HelloWorld", "HelloWorld"), - QCoreApplication.translate("HelloWorld", "HelloWorld"), - ) - return - - -if __name__ == "__main__": - pass diff --git a/qgis-app/plugins/tests/HelloWorld/1.2-md-txt-incomplete/HelloWorld/__init__.py b/qgis-app/plugins/tests/HelloWorld/1.2-md-txt-incomplete/HelloWorld/__init__.py deleted file mode 100644 index f70b70c6..00000000 --- a/qgis-app/plugins/tests/HelloWorld/1.2-md-txt-incomplete/HelloWorld/__init__.py +++ /dev/null @@ -1,41 +0,0 @@ -# -*- coding: utf-8 -*- -""" - This script initializes the plugin, making it known to QGIS. -""" - - -def name(): - return "HelloWorld" - - -def description(): - return "HelloWorld" - - -def version(): - return "Version 1.1" - - -def qgisMinimumVersion(): - return "1.0" - - -def classFactory(iface): - from HelloWorld import HelloWorld - - return HelloWorld(iface) - - -def icon(): - """ - Icon - """ - return "icon.png" - - -def author(): - return "Alessandro Secondo" - - -def email(): - return "email2@email.com" diff --git a/qgis-app/plugins/tests/HelloWorld/1.2-md-txt-incomplete/HelloWorld/icon.png b/qgis-app/plugins/tests/HelloWorld/1.2-md-txt-incomplete/HelloWorld/icon.png deleted file mode 100644 index c043d921..00000000 Binary files a/qgis-app/plugins/tests/HelloWorld/1.2-md-txt-incomplete/HelloWorld/icon.png and /dev/null differ diff --git a/qgis-app/plugins/tests/HelloWorld/1.2-md-txt-incomplete/HelloWorld/metadata.txt b/qgis-app/plugins/tests/HelloWorld/1.2-md-txt-incomplete/HelloWorld/metadata.txt deleted file mode 100644 index e77b9aeb..00000000 --- a/qgis-app/plugins/tests/HelloWorld/1.2-md-txt-incomplete/HelloWorld/metadata.txt +++ /dev/null @@ -1,2 +0,0 @@ -[general] -nodata=true diff --git a/qgis-app/plugins/tests/HelloWorld/1.2-no-icon/HelloWorld/HelloWorld.py b/qgis-app/plugins/tests/HelloWorld/1.2-no-icon/HelloWorld/HelloWorld.py deleted file mode 100644 index 45bdac04..00000000 --- a/qgis-app/plugins/tests/HelloWorld/1.2-no-icon/HelloWorld/HelloWorld.py +++ /dev/null @@ -1,40 +0,0 @@ -# -*- coding: utf-8 -*- -# Import the PyQt and QGIS libraries -from PyQt4.QtCore import * -from PyQt4.QtGui import * -from qgis.core import * - - -class HelloWorld: - def __init__(self, iface): - # Save reference to the QGIS interface - self.iface = iface - self.canvas = iface.mapCanvas() - - def initGui(self): - # Create action that will start plugin - self.action = QAction( - QIcon(":/plugins/"), "&HelloWorld", self.iface.mainWindow() - ) - # connect the action to the run method - QObject.connect(self.action, SIGNAL("activated()"), self.hello_world) - - # Add toolbar button and menu item - self.iface.addPluginToMenu("HelloWorld", self.action) - - def unload(self): - # Remove the plugin menu item and icon - self.iface.removePluginMenu("HelloWorld", self.action) - - # run - def hello_world(self): - QMessageBox.information( - self.iface.mainWindow(), - QCoreApplication.translate("HelloWorld", "HelloWorld"), - QCoreApplication.translate("HelloWorld", "HelloWorld"), - ) - return - - -if __name__ == "__main__": - pass diff --git a/qgis-app/plugins/tests/HelloWorld/1.2-no-icon/HelloWorld/__init__.py b/qgis-app/plugins/tests/HelloWorld/1.2-no-icon/HelloWorld/__init__.py deleted file mode 100644 index fe04c5c0..00000000 --- a/qgis-app/plugins/tests/HelloWorld/1.2-no-icon/HelloWorld/__init__.py +++ /dev/null @@ -1,26 +0,0 @@ -# -*- coding: utf-8 -*- -""" - This script initializes the plugin, making it known to QGIS. -""" - - -def name(): - return "HelloWorld" - - -def description(): - return "HelloWorld" - - -def version(): - return "Version 1.1" - - -def qgisMinimumVersion(): - return "1.0" - - -def classFactory(iface): - from HelloWorld import HelloWorld - - return HelloWorld(iface) diff --git a/qgis-app/plugins/tests/HelloWorld/1.2-no-icon/HelloWorld/icon.png b/qgis-app/plugins/tests/HelloWorld/1.2-no-icon/HelloWorld/icon.png deleted file mode 100644 index c043d921..00000000 Binary files a/qgis-app/plugins/tests/HelloWorld/1.2-no-icon/HelloWorld/icon.png and /dev/null differ diff --git a/qgis-app/plugins/tests/HelloWorld/1.2-no-icon/HelloWorld/metadata.txt b/qgis-app/plugins/tests/HelloWorld/1.2-no-icon/HelloWorld/metadata.txt deleted file mode 100644 index 2aa2d5be..00000000 --- a/qgis-app/plugins/tests/HelloWorld/1.2-no-icon/HelloWorld/metadata.txt +++ /dev/null @@ -1,21 +0,0 @@ -[general] -name=HelloWorld -qgisMinimumVersion=1.6 -description=This is a plugin for greeting the - (going multiline) world -version=version 1.2 - - -changelog=this is a very - very
- very - very - very - very long multiline changelog - - -tags=wkt,raster,hello world - -tracker=http://bugs.itopen.it -homepage=http://www.itopen.it -repository=http://www.itopen.it/repo diff --git a/qgis-app/plugins/tests/HelloWorld/1.2-qgs-1.6-md-init/HelloWorld/HelloWorld.py b/qgis-app/plugins/tests/HelloWorld/1.2-qgs-1.6-md-init/HelloWorld/HelloWorld.py deleted file mode 100644 index 45bdac04..00000000 --- a/qgis-app/plugins/tests/HelloWorld/1.2-qgs-1.6-md-init/HelloWorld/HelloWorld.py +++ /dev/null @@ -1,40 +0,0 @@ -# -*- coding: utf-8 -*- -# Import the PyQt and QGIS libraries -from PyQt4.QtCore import * -from PyQt4.QtGui import * -from qgis.core import * - - -class HelloWorld: - def __init__(self, iface): - # Save reference to the QGIS interface - self.iface = iface - self.canvas = iface.mapCanvas() - - def initGui(self): - # Create action that will start plugin - self.action = QAction( - QIcon(":/plugins/"), "&HelloWorld", self.iface.mainWindow() - ) - # connect the action to the run method - QObject.connect(self.action, SIGNAL("activated()"), self.hello_world) - - # Add toolbar button and menu item - self.iface.addPluginToMenu("HelloWorld", self.action) - - def unload(self): - # Remove the plugin menu item and icon - self.iface.removePluginMenu("HelloWorld", self.action) - - # run - def hello_world(self): - QMessageBox.information( - self.iface.mainWindow(), - QCoreApplication.translate("HelloWorld", "HelloWorld"), - QCoreApplication.translate("HelloWorld", "HelloWorld"), - ) - return - - -if __name__ == "__main__": - pass diff --git a/qgis-app/plugins/tests/HelloWorld/1.2-qgs-1.6-md-init/HelloWorld/__init__.py b/qgis-app/plugins/tests/HelloWorld/1.2-qgs-1.6-md-init/HelloWorld/__init__.py deleted file mode 100644 index 5f7dc3df..00000000 --- a/qgis-app/plugins/tests/HelloWorld/1.2-qgs-1.6-md-init/HelloWorld/__init__.py +++ /dev/null @@ -1,33 +0,0 @@ -# -*- coding: utf-8 -*- -""" - This script initializes the plugin, making it known to QGIS. -""" - - -def name(): - return "HelloWorld" - - -def description(): - return "HelloWorld" - - -def version(): - return "Version 1.1" - - -def qgisMinimumVersion(): - return "1.0" - - -def classFactory(iface): - from HelloWorld import HelloWorld - - return HelloWorld(iface) - - -def icon(): - """ - Icon - """ - return "icon.png" diff --git a/qgis-app/plugins/tests/HelloWorld/1.2-qgs-1.6-md-init/HelloWorld/icon.png b/qgis-app/plugins/tests/HelloWorld/1.2-qgs-1.6-md-init/HelloWorld/icon.png deleted file mode 100644 index c043d921..00000000 Binary files a/qgis-app/plugins/tests/HelloWorld/1.2-qgs-1.6-md-init/HelloWorld/icon.png and /dev/null differ diff --git a/qgis-app/plugins/tests/HelloWorld/1.2-qgs-1.6-md-init/HelloWorld/metadata.txt b/qgis-app/plugins/tests/HelloWorld/1.2-qgs-1.6-md-init/HelloWorld/metadata.txt deleted file mode 100644 index 2aa2d5be..00000000 --- a/qgis-app/plugins/tests/HelloWorld/1.2-qgs-1.6-md-init/HelloWorld/metadata.txt +++ /dev/null @@ -1,21 +0,0 @@ -[general] -name=HelloWorld -qgisMinimumVersion=1.6 -description=This is a plugin for greeting the - (going multiline) world -version=version 1.2 - - -changelog=this is a very - very
- very - very - very - very long multiline changelog - - -tags=wkt,raster,hello world - -tracker=http://bugs.itopen.it -homepage=http://www.itopen.it -repository=http://www.itopen.it/repo diff --git a/qgis-app/plugins/tests/HelloWorld/1.2-qgs-1.6-no-init/HelloWorld/HelloWorld.py b/qgis-app/plugins/tests/HelloWorld/1.2-qgs-1.6-no-init/HelloWorld/HelloWorld.py deleted file mode 100644 index 45bdac04..00000000 --- a/qgis-app/plugins/tests/HelloWorld/1.2-qgs-1.6-no-init/HelloWorld/HelloWorld.py +++ /dev/null @@ -1,40 +0,0 @@ -# -*- coding: utf-8 -*- -# Import the PyQt and QGIS libraries -from PyQt4.QtCore import * -from PyQt4.QtGui import * -from qgis.core import * - - -class HelloWorld: - def __init__(self, iface): - # Save reference to the QGIS interface - self.iface = iface - self.canvas = iface.mapCanvas() - - def initGui(self): - # Create action that will start plugin - self.action = QAction( - QIcon(":/plugins/"), "&HelloWorld", self.iface.mainWindow() - ) - # connect the action to the run method - QObject.connect(self.action, SIGNAL("activated()"), self.hello_world) - - # Add toolbar button and menu item - self.iface.addPluginToMenu("HelloWorld", self.action) - - def unload(self): - # Remove the plugin menu item and icon - self.iface.removePluginMenu("HelloWorld", self.action) - - # run - def hello_world(self): - QMessageBox.information( - self.iface.mainWindow(), - QCoreApplication.translate("HelloWorld", "HelloWorld"), - QCoreApplication.translate("HelloWorld", "HelloWorld"), - ) - return - - -if __name__ == "__main__": - pass diff --git a/qgis-app/plugins/tests/HelloWorld/1.2-qgs-1.6-no-init/HelloWorld/__init__.py b/qgis-app/plugins/tests/HelloWorld/1.2-qgs-1.6-no-init/HelloWorld/__init__.py deleted file mode 100644 index e9feb7d9..00000000 --- a/qgis-app/plugins/tests/HelloWorld/1.2-qgs-1.6-no-init/HelloWorld/__init__.py +++ /dev/null @@ -1,4 +0,0 @@ -# -*- coding: utf-8 -*- -""" - This script initializes the plugin, making it known to QGIS. -""" diff --git a/qgis-app/plugins/tests/HelloWorld/1.2-qgs-1.6-no-init/HelloWorld/icon.png b/qgis-app/plugins/tests/HelloWorld/1.2-qgs-1.6-no-init/HelloWorld/icon.png deleted file mode 100644 index c043d921..00000000 Binary files a/qgis-app/plugins/tests/HelloWorld/1.2-qgs-1.6-no-init/HelloWorld/icon.png and /dev/null differ diff --git a/qgis-app/plugins/tests/HelloWorld/1.2-qgs-1.6-no-init/HelloWorld/metadata.txt b/qgis-app/plugins/tests/HelloWorld/1.2-qgs-1.6-no-init/HelloWorld/metadata.txt deleted file mode 100644 index 2aa2d5be..00000000 --- a/qgis-app/plugins/tests/HelloWorld/1.2-qgs-1.6-no-init/HelloWorld/metadata.txt +++ /dev/null @@ -1,21 +0,0 @@ -[general] -name=HelloWorld -qgisMinimumVersion=1.6 -description=This is a plugin for greeting the - (going multiline) world -version=version 1.2 - - -changelog=this is a very - very
- very - very - very - very long multiline changelog - - -tags=wkt,raster,hello world - -tracker=http://bugs.itopen.it -homepage=http://www.itopen.it -repository=http://www.itopen.it/repo diff --git a/qgis-app/plugins/tests/HelloWorld/1.2-wierdname/HelloWorld/HelloWorld.py b/qgis-app/plugins/tests/HelloWorld/1.2-wierdname/HelloWorld/HelloWorld.py deleted file mode 100644 index 45bdac04..00000000 --- a/qgis-app/plugins/tests/HelloWorld/1.2-wierdname/HelloWorld/HelloWorld.py +++ /dev/null @@ -1,40 +0,0 @@ -# -*- coding: utf-8 -*- -# Import the PyQt and QGIS libraries -from PyQt4.QtCore import * -from PyQt4.QtGui import * -from qgis.core import * - - -class HelloWorld: - def __init__(self, iface): - # Save reference to the QGIS interface - self.iface = iface - self.canvas = iface.mapCanvas() - - def initGui(self): - # Create action that will start plugin - self.action = QAction( - QIcon(":/plugins/"), "&HelloWorld", self.iface.mainWindow() - ) - # connect the action to the run method - QObject.connect(self.action, SIGNAL("activated()"), self.hello_world) - - # Add toolbar button and menu item - self.iface.addPluginToMenu("HelloWorld", self.action) - - def unload(self): - # Remove the plugin menu item and icon - self.iface.removePluginMenu("HelloWorld", self.action) - - # run - def hello_world(self): - QMessageBox.information( - self.iface.mainWindow(), - QCoreApplication.translate("HelloWorld", "HelloWorld"), - QCoreApplication.translate("HelloWorld", "HelloWorld"), - ) - return - - -if __name__ == "__main__": - pass diff --git a/qgis-app/plugins/tests/HelloWorld/1.2-wierdname/HelloWorld/__init__.py b/qgis-app/plugins/tests/HelloWorld/1.2-wierdname/HelloWorld/__init__.py deleted file mode 100644 index 5f7dc3df..00000000 --- a/qgis-app/plugins/tests/HelloWorld/1.2-wierdname/HelloWorld/__init__.py +++ /dev/null @@ -1,33 +0,0 @@ -# -*- coding: utf-8 -*- -""" - This script initializes the plugin, making it known to QGIS. -""" - - -def name(): - return "HelloWorld" - - -def description(): - return "HelloWorld" - - -def version(): - return "Version 1.1" - - -def qgisMinimumVersion(): - return "1.0" - - -def classFactory(iface): - from HelloWorld import HelloWorld - - return HelloWorld(iface) - - -def icon(): - """ - Icon - """ - return "icon.png" diff --git a/qgis-app/plugins/tests/HelloWorld/1.2-wierdname/HelloWorld/icon.png b/qgis-app/plugins/tests/HelloWorld/1.2-wierdname/HelloWorld/icon.png deleted file mode 100644 index c043d921..00000000 Binary files a/qgis-app/plugins/tests/HelloWorld/1.2-wierdname/HelloWorld/icon.png and /dev/null differ diff --git a/qgis-app/plugins/tests/HelloWorld/1.2-wierdname/HelloWorld/metadata.txt b/qgis-app/plugins/tests/HelloWorld/1.2-wierdname/HelloWorld/metadata.txt deleted file mode 100644 index c13f54ef..00000000 --- a/qgis-app/plugins/tests/HelloWorld/1.2-wierdname/HelloWorld/metadata.txt +++ /dev/null @@ -1,24 +0,0 @@ -[general] -name=WierdName -qgisMinimumVersion=1.8 -description=This is a plugin for greeting the - (going multiline) world -version=version 1.2 -author=apasotti@gmail.com -email=apasotti@gmail.com -about=about something -changelog=this is a very - very
- very - very - very - very long multiline changelog - - -tags=wkt,raster - -tracker=http://bugs.itopen.it -homepage=http://www.itopen.it -repository=http://www.itopen.it/repo - -icon=icon.png diff --git a/qgis-app/plugins/tests/HelloWorld/1.2/HelloWorld/HelloWorld.py b/qgis-app/plugins/tests/HelloWorld/1.2/HelloWorld/HelloWorld.py deleted file mode 100644 index 45bdac04..00000000 --- a/qgis-app/plugins/tests/HelloWorld/1.2/HelloWorld/HelloWorld.py +++ /dev/null @@ -1,40 +0,0 @@ -# -*- coding: utf-8 -*- -# Import the PyQt and QGIS libraries -from PyQt4.QtCore import * -from PyQt4.QtGui import * -from qgis.core import * - - -class HelloWorld: - def __init__(self, iface): - # Save reference to the QGIS interface - self.iface = iface - self.canvas = iface.mapCanvas() - - def initGui(self): - # Create action that will start plugin - self.action = QAction( - QIcon(":/plugins/"), "&HelloWorld", self.iface.mainWindow() - ) - # connect the action to the run method - QObject.connect(self.action, SIGNAL("activated()"), self.hello_world) - - # Add toolbar button and menu item - self.iface.addPluginToMenu("HelloWorld", self.action) - - def unload(self): - # Remove the plugin menu item and icon - self.iface.removePluginMenu("HelloWorld", self.action) - - # run - def hello_world(self): - QMessageBox.information( - self.iface.mainWindow(), - QCoreApplication.translate("HelloWorld", "HelloWorld"), - QCoreApplication.translate("HelloWorld", "HelloWorld"), - ) - return - - -if __name__ == "__main__": - pass diff --git a/qgis-app/plugins/tests/HelloWorld/1.2/HelloWorld/__init__.py b/qgis-app/plugins/tests/HelloWorld/1.2/HelloWorld/__init__.py deleted file mode 100644 index 5f7dc3df..00000000 --- a/qgis-app/plugins/tests/HelloWorld/1.2/HelloWorld/__init__.py +++ /dev/null @@ -1,33 +0,0 @@ -# -*- coding: utf-8 -*- -""" - This script initializes the plugin, making it known to QGIS. -""" - - -def name(): - return "HelloWorld" - - -def description(): - return "HelloWorld" - - -def version(): - return "Version 1.1" - - -def qgisMinimumVersion(): - return "1.0" - - -def classFactory(iface): - from HelloWorld import HelloWorld - - return HelloWorld(iface) - - -def icon(): - """ - Icon - """ - return "icon.png" diff --git a/qgis-app/plugins/tests/HelloWorld/1.2/HelloWorld/icon.png b/qgis-app/plugins/tests/HelloWorld/1.2/HelloWorld/icon.png deleted file mode 100644 index c043d921..00000000 Binary files a/qgis-app/plugins/tests/HelloWorld/1.2/HelloWorld/icon.png and /dev/null differ diff --git a/qgis-app/plugins/tests/HelloWorld/1.2/HelloWorld/metadata.txt b/qgis-app/plugins/tests/HelloWorld/1.2/HelloWorld/metadata.txt deleted file mode 100644 index 91b7081d..00000000 --- a/qgis-app/plugins/tests/HelloWorld/1.2/HelloWorld/metadata.txt +++ /dev/null @@ -1,23 +0,0 @@ -[general] -name=HelloWorld -qgisMinimumVersion=1.6 -description=This is a plugin for greeting the - (going multiline) world -version=version 1.2 - - -changelog=this is a very - very
- very - very - very - very long multiline changelog - - -tags=wkt,raster,hello world - -tracker=http://bugs.itopen.it -homepage=http://www.itopen.it -repository=http://www.itopen.it/repo - -icon=icon.png diff --git a/qgis-app/plugins/tests/HelloWorld/1.3-full-md-no-init/HelloWorld/HelloWorld.py b/qgis-app/plugins/tests/HelloWorld/1.3-full-md-no-init/HelloWorld/HelloWorld.py deleted file mode 100644 index 45bdac04..00000000 --- a/qgis-app/plugins/tests/HelloWorld/1.3-full-md-no-init/HelloWorld/HelloWorld.py +++ /dev/null @@ -1,40 +0,0 @@ -# -*- coding: utf-8 -*- -# Import the PyQt and QGIS libraries -from PyQt4.QtCore import * -from PyQt4.QtGui import * -from qgis.core import * - - -class HelloWorld: - def __init__(self, iface): - # Save reference to the QGIS interface - self.iface = iface - self.canvas = iface.mapCanvas() - - def initGui(self): - # Create action that will start plugin - self.action = QAction( - QIcon(":/plugins/"), "&HelloWorld", self.iface.mainWindow() - ) - # connect the action to the run method - QObject.connect(self.action, SIGNAL("activated()"), self.hello_world) - - # Add toolbar button and menu item - self.iface.addPluginToMenu("HelloWorld", self.action) - - def unload(self): - # Remove the plugin menu item and icon - self.iface.removePluginMenu("HelloWorld", self.action) - - # run - def hello_world(self): - QMessageBox.information( - self.iface.mainWindow(), - QCoreApplication.translate("HelloWorld", "HelloWorld"), - QCoreApplication.translate("HelloWorld", "HelloWorld"), - ) - return - - -if __name__ == "__main__": - pass diff --git a/qgis-app/plugins/tests/HelloWorld/1.3-full-md-no-init/HelloWorld/__init__.py b/qgis-app/plugins/tests/HelloWorld/1.3-full-md-no-init/HelloWorld/__init__.py deleted file mode 100644 index e9feb7d9..00000000 --- a/qgis-app/plugins/tests/HelloWorld/1.3-full-md-no-init/HelloWorld/__init__.py +++ /dev/null @@ -1,4 +0,0 @@ -# -*- coding: utf-8 -*- -""" - This script initializes the plugin, making it known to QGIS. -""" diff --git a/qgis-app/plugins/tests/HelloWorld/1.3-full-md-no-init/HelloWorld/icon.png b/qgis-app/plugins/tests/HelloWorld/1.3-full-md-no-init/HelloWorld/icon.png deleted file mode 100644 index 67d47930..00000000 Binary files a/qgis-app/plugins/tests/HelloWorld/1.3-full-md-no-init/HelloWorld/icon.png and /dev/null differ diff --git a/qgis-app/plugins/tests/HelloWorld/1.3-full-md-no-init/HelloWorld/metadata.txt b/qgis-app/plugins/tests/HelloWorld/1.3-full-md-no-init/HelloWorld/metadata.txt deleted file mode 100644 index 9e35f413..00000000 --- a/qgis-app/plugins/tests/HelloWorld/1.3-full-md-no-init/HelloWorld/metadata.txt +++ /dev/null @@ -1,26 +0,0 @@ -[general] -name=HelloWorld -qgisMinimumVersion=1.8 -description=This is a plugin for greeting the - (going multiline) world, version is 1.3 -version=version 1.3 -author=Alessandro Secondo -email=email2@email.com - - -changelog=this is a very - very
- very - very - very - very long multiline changelog - - -tags=wkt,raster,hello world - -tracker=http://bugs.itopen.it -homepage=http://www.itopen.it -repository=http://www.itopen.it/repo - -; change icon... -icon=icon.png diff --git a/qgis-app/plugins/tests/HelloWorld/1.5-full-md-no-init-experimental/HelloWorld/HelloWorld.py b/qgis-app/plugins/tests/HelloWorld/1.5-full-md-no-init-experimental/HelloWorld/HelloWorld.py deleted file mode 100644 index 45bdac04..00000000 --- a/qgis-app/plugins/tests/HelloWorld/1.5-full-md-no-init-experimental/HelloWorld/HelloWorld.py +++ /dev/null @@ -1,40 +0,0 @@ -# -*- coding: utf-8 -*- -# Import the PyQt and QGIS libraries -from PyQt4.QtCore import * -from PyQt4.QtGui import * -from qgis.core import * - - -class HelloWorld: - def __init__(self, iface): - # Save reference to the QGIS interface - self.iface = iface - self.canvas = iface.mapCanvas() - - def initGui(self): - # Create action that will start plugin - self.action = QAction( - QIcon(":/plugins/"), "&HelloWorld", self.iface.mainWindow() - ) - # connect the action to the run method - QObject.connect(self.action, SIGNAL("activated()"), self.hello_world) - - # Add toolbar button and menu item - self.iface.addPluginToMenu("HelloWorld", self.action) - - def unload(self): - # Remove the plugin menu item and icon - self.iface.removePluginMenu("HelloWorld", self.action) - - # run - def hello_world(self): - QMessageBox.information( - self.iface.mainWindow(), - QCoreApplication.translate("HelloWorld", "HelloWorld"), - QCoreApplication.translate("HelloWorld", "HelloWorld"), - ) - return - - -if __name__ == "__main__": - pass diff --git a/qgis-app/plugins/tests/HelloWorld/1.5-full-md-no-init-experimental/HelloWorld/__init__.py b/qgis-app/plugins/tests/HelloWorld/1.5-full-md-no-init-experimental/HelloWorld/__init__.py deleted file mode 100644 index eff9426a..00000000 --- a/qgis-app/plugins/tests/HelloWorld/1.5-full-md-no-init-experimental/HelloWorld/__init__.py +++ /dev/null @@ -1,10 +0,0 @@ -# -*- coding: utf-8 -*- -""" - This script initializes the plugin, making it known to QGIS. -""" - - -def classFactory(iface): - from HelloWorld import HelloWorld - - return HelloWorld(iface) diff --git a/qgis-app/plugins/tests/HelloWorld/1.5-full-md-no-init-experimental/HelloWorld/icon.png b/qgis-app/plugins/tests/HelloWorld/1.5-full-md-no-init-experimental/HelloWorld/icon.png deleted file mode 100644 index 67d47930..00000000 Binary files a/qgis-app/plugins/tests/HelloWorld/1.5-full-md-no-init-experimental/HelloWorld/icon.png and /dev/null differ diff --git a/qgis-app/plugins/tests/HelloWorld/1.5-full-md-no-init-experimental/HelloWorld/metadata.txt b/qgis-app/plugins/tests/HelloWorld/1.5-full-md-no-init-experimental/HelloWorld/metadata.txt deleted file mode 100644 index 778919e0..00000000 --- a/qgis-app/plugins/tests/HelloWorld/1.5-full-md-no-init-experimental/HelloWorld/metadata.txt +++ /dev/null @@ -1,31 +0,0 @@ -[general] -name=HelloWorld -qgisMinimumVersion=1.8 -description=This is a test plugin for greeting the - (going multiline) world, version is 1.5 -version=version 1.5 -author=Alessandro Pasotti -email=apasotti@gmail.com - - -changelog=this is a very - very
- very - very - very - very long multiline changelog - - -tags= wkt, raster,hello world, spaced tag , tag - -tracker=http://bugs.itopen.it -homepage=http://www.itopen.it -repository=http://www.itopen.it/repo - - - -experimental=True - - -; change icon... -icon=icon.png diff --git a/qgis-app/plugins/tests/HelloWorld/1.6-full-md-no-init-unicode-error/HelloWorld/HelloWorld.py b/qgis-app/plugins/tests/HelloWorld/1.6-full-md-no-init-unicode-error/HelloWorld/HelloWorld.py deleted file mode 100644 index 45bdac04..00000000 --- a/qgis-app/plugins/tests/HelloWorld/1.6-full-md-no-init-unicode-error/HelloWorld/HelloWorld.py +++ /dev/null @@ -1,40 +0,0 @@ -# -*- coding: utf-8 -*- -# Import the PyQt and QGIS libraries -from PyQt4.QtCore import * -from PyQt4.QtGui import * -from qgis.core import * - - -class HelloWorld: - def __init__(self, iface): - # Save reference to the QGIS interface - self.iface = iface - self.canvas = iface.mapCanvas() - - def initGui(self): - # Create action that will start plugin - self.action = QAction( - QIcon(":/plugins/"), "&HelloWorld", self.iface.mainWindow() - ) - # connect the action to the run method - QObject.connect(self.action, SIGNAL("activated()"), self.hello_world) - - # Add toolbar button and menu item - self.iface.addPluginToMenu("HelloWorld", self.action) - - def unload(self): - # Remove the plugin menu item and icon - self.iface.removePluginMenu("HelloWorld", self.action) - - # run - def hello_world(self): - QMessageBox.information( - self.iface.mainWindow(), - QCoreApplication.translate("HelloWorld", "HelloWorld"), - QCoreApplication.translate("HelloWorld", "HelloWorld"), - ) - return - - -if __name__ == "__main__": - pass diff --git a/qgis-app/plugins/tests/HelloWorld/1.6-full-md-no-init-unicode-error/HelloWorld/__init__.py b/qgis-app/plugins/tests/HelloWorld/1.6-full-md-no-init-unicode-error/HelloWorld/__init__.py deleted file mode 100644 index eff9426a..00000000 --- a/qgis-app/plugins/tests/HelloWorld/1.6-full-md-no-init-unicode-error/HelloWorld/__init__.py +++ /dev/null @@ -1,10 +0,0 @@ -# -*- coding: utf-8 -*- -""" - This script initializes the plugin, making it known to QGIS. -""" - - -def classFactory(iface): - from HelloWorld import HelloWorld - - return HelloWorld(iface) diff --git a/qgis-app/plugins/tests/HelloWorld/1.6-full-md-no-init-unicode-error/HelloWorld/icon.png b/qgis-app/plugins/tests/HelloWorld/1.6-full-md-no-init-unicode-error/HelloWorld/icon.png deleted file mode 100644 index 67d47930..00000000 Binary files a/qgis-app/plugins/tests/HelloWorld/1.6-full-md-no-init-unicode-error/HelloWorld/icon.png and /dev/null differ diff --git a/qgis-app/plugins/tests/HelloWorld/1.6-full-md-no-init-unicode-error/HelloWorld/metadata.txt b/qgis-app/plugins/tests/HelloWorld/1.6-full-md-no-init-unicode-error/HelloWorld/metadata.txt deleted file mode 100644 index 1fb4daf4..00000000 --- a/qgis-app/plugins/tests/HelloWorld/1.6-full-md-no-init-unicode-error/HelloWorld/metadata.txt +++ /dev/null @@ -1,31 +0,0 @@ -[general] -name=HelloWorld -qgisMinimumVersion=1.8 -description=This is a test plugin for greeting the - (going multiline) world, version is 1.6 -version=version 1.6 -author=Alessandrò Pasottì -email=apasotti@gmail.com - - -changelog=this is a very - very
- very - very - very - very long multiline changelog - - -tags= wkt, raster,hello world, spaced tag , tag - -tracker=http://bugs.itopen.it -homepage=http://www.itopen.it -repository=http://www.itopen.it/repo - - - -experimental=True - - -; change icon... -icon=icon.png diff --git a/qgis-app/plugins/tests/HelloWorld/1.7-full-md-no-init-subfolder-icon/HelloWorld/HelloWorld.py b/qgis-app/plugins/tests/HelloWorld/1.7-full-md-no-init-subfolder-icon/HelloWorld/HelloWorld.py deleted file mode 100644 index 45bdac04..00000000 --- a/qgis-app/plugins/tests/HelloWorld/1.7-full-md-no-init-subfolder-icon/HelloWorld/HelloWorld.py +++ /dev/null @@ -1,40 +0,0 @@ -# -*- coding: utf-8 -*- -# Import the PyQt and QGIS libraries -from PyQt4.QtCore import * -from PyQt4.QtGui import * -from qgis.core import * - - -class HelloWorld: - def __init__(self, iface): - # Save reference to the QGIS interface - self.iface = iface - self.canvas = iface.mapCanvas() - - def initGui(self): - # Create action that will start plugin - self.action = QAction( - QIcon(":/plugins/"), "&HelloWorld", self.iface.mainWindow() - ) - # connect the action to the run method - QObject.connect(self.action, SIGNAL("activated()"), self.hello_world) - - # Add toolbar button and menu item - self.iface.addPluginToMenu("HelloWorld", self.action) - - def unload(self): - # Remove the plugin menu item and icon - self.iface.removePluginMenu("HelloWorld", self.action) - - # run - def hello_world(self): - QMessageBox.information( - self.iface.mainWindow(), - QCoreApplication.translate("HelloWorld", "HelloWorld"), - QCoreApplication.translate("HelloWorld", "HelloWorld"), - ) - return - - -if __name__ == "__main__": - pass diff --git a/qgis-app/plugins/tests/HelloWorld/1.7-full-md-no-init-subfolder-icon/HelloWorld/__init__.py b/qgis-app/plugins/tests/HelloWorld/1.7-full-md-no-init-subfolder-icon/HelloWorld/__init__.py deleted file mode 100644 index eff9426a..00000000 --- a/qgis-app/plugins/tests/HelloWorld/1.7-full-md-no-init-subfolder-icon/HelloWorld/__init__.py +++ /dev/null @@ -1,10 +0,0 @@ -# -*- coding: utf-8 -*- -""" - This script initializes the plugin, making it known to QGIS. -""" - - -def classFactory(iface): - from HelloWorld import HelloWorld - - return HelloWorld(iface) diff --git a/qgis-app/plugins/tests/HelloWorld/1.7-full-md-no-init-subfolder-icon/HelloWorld/icons/icon.png b/qgis-app/plugins/tests/HelloWorld/1.7-full-md-no-init-subfolder-icon/HelloWorld/icons/icon.png deleted file mode 100644 index 681cf0cb..00000000 Binary files a/qgis-app/plugins/tests/HelloWorld/1.7-full-md-no-init-subfolder-icon/HelloWorld/icons/icon.png and /dev/null differ diff --git a/qgis-app/plugins/tests/HelloWorld/1.7-full-md-no-init-subfolder-icon/HelloWorld/metadata.txt b/qgis-app/plugins/tests/HelloWorld/1.7-full-md-no-init-subfolder-icon/HelloWorld/metadata.txt deleted file mode 100644 index ad48068a..00000000 --- a/qgis-app/plugins/tests/HelloWorld/1.7-full-md-no-init-subfolder-icon/HelloWorld/metadata.txt +++ /dev/null @@ -1,31 +0,0 @@ -[general] -name=HelloWorld -qgisMinimumVersion=1.8 -description=This is a test plugin for greeting the - (going multiline) world, version is 1.7 -version=version 1.7 -author=Alessandro Pasotti èàò -email=apasotti@gmail.com - - -changelog=this is a very - very
- very - very - very - very long multiline changelog - - -tags= wkt, raster,hello world, spaced tag , tag - -tracker=http://bugs.itopen.it -homepage=http://www.itopen.it -repository=http://www.itopen.it/repo - - - -experimental=True - - -; change icon... -icon=icons/icon.png diff --git a/qgis-app/plugins/tests/HelloWorld/1.8-author-slashes-error/HelloWorld/HelloWorld.py b/qgis-app/plugins/tests/HelloWorld/1.8-author-slashes-error/HelloWorld/HelloWorld.py deleted file mode 100644 index 45bdac04..00000000 --- a/qgis-app/plugins/tests/HelloWorld/1.8-author-slashes-error/HelloWorld/HelloWorld.py +++ /dev/null @@ -1,40 +0,0 @@ -# -*- coding: utf-8 -*- -# Import the PyQt and QGIS libraries -from PyQt4.QtCore import * -from PyQt4.QtGui import * -from qgis.core import * - - -class HelloWorld: - def __init__(self, iface): - # Save reference to the QGIS interface - self.iface = iface - self.canvas = iface.mapCanvas() - - def initGui(self): - # Create action that will start plugin - self.action = QAction( - QIcon(":/plugins/"), "&HelloWorld", self.iface.mainWindow() - ) - # connect the action to the run method - QObject.connect(self.action, SIGNAL("activated()"), self.hello_world) - - # Add toolbar button and menu item - self.iface.addPluginToMenu("HelloWorld", self.action) - - def unload(self): - # Remove the plugin menu item and icon - self.iface.removePluginMenu("HelloWorld", self.action) - - # run - def hello_world(self): - QMessageBox.information( - self.iface.mainWindow(), - QCoreApplication.translate("HelloWorld", "HelloWorld"), - QCoreApplication.translate("HelloWorld", "HelloWorld"), - ) - return - - -if __name__ == "__main__": - pass diff --git a/qgis-app/plugins/tests/HelloWorld/1.8-author-slashes-error/HelloWorld/__init__.py b/qgis-app/plugins/tests/HelloWorld/1.8-author-slashes-error/HelloWorld/__init__.py deleted file mode 100644 index e9feb7d9..00000000 --- a/qgis-app/plugins/tests/HelloWorld/1.8-author-slashes-error/HelloWorld/__init__.py +++ /dev/null @@ -1,4 +0,0 @@ -# -*- coding: utf-8 -*- -""" - This script initializes the plugin, making it known to QGIS. -""" diff --git a/qgis-app/plugins/tests/HelloWorld/1.8-author-slashes-error/HelloWorld/icons/icon.png b/qgis-app/plugins/tests/HelloWorld/1.8-author-slashes-error/HelloWorld/icons/icon.png deleted file mode 100644 index 681cf0cb..00000000 Binary files a/qgis-app/plugins/tests/HelloWorld/1.8-author-slashes-error/HelloWorld/icons/icon.png and /dev/null differ diff --git a/qgis-app/plugins/tests/HelloWorld/1.8-author-slashes-error/HelloWorld/metadata.txt b/qgis-app/plugins/tests/HelloWorld/1.8-author-slashes-error/HelloWorld/metadata.txt deleted file mode 100644 index a6bdae93..00000000 --- a/qgis-app/plugins/tests/HelloWorld/1.8-author-slashes-error/HelloWorld/metadata.txt +++ /dev/null @@ -1,31 +0,0 @@ -[general] -name=HelloWorld -qgisMinimumVersion=1.8 -description=This is a test plugin for greeting the - (going multiline) world, version is 1.7 -version=version 1.8 -author=Alessandro/Pasotti èàò -email=apasotti@gmail.com - - -changelog=this is a very - very
- very - very - very - very long multiline changelog - - -tags= wkt, raster,hello world, spaced tag , tag - -tracker=http://bugs.itopen.it -homepage=http://www.itopen.it -repository=http://www.itopen.it/repo - - - -experimental=True - - -; change icon... -icon=icons/icon.png diff --git a/qgis-app/plugins/tests/HelloWorld/1.9-full-md-max_qgs_version/HelloWorld/HelloWorld.py b/qgis-app/plugins/tests/HelloWorld/1.9-full-md-max_qgs_version/HelloWorld/HelloWorld.py deleted file mode 100644 index 45bdac04..00000000 --- a/qgis-app/plugins/tests/HelloWorld/1.9-full-md-max_qgs_version/HelloWorld/HelloWorld.py +++ /dev/null @@ -1,40 +0,0 @@ -# -*- coding: utf-8 -*- -# Import the PyQt and QGIS libraries -from PyQt4.QtCore import * -from PyQt4.QtGui import * -from qgis.core import * - - -class HelloWorld: - def __init__(self, iface): - # Save reference to the QGIS interface - self.iface = iface - self.canvas = iface.mapCanvas() - - def initGui(self): - # Create action that will start plugin - self.action = QAction( - QIcon(":/plugins/"), "&HelloWorld", self.iface.mainWindow() - ) - # connect the action to the run method - QObject.connect(self.action, SIGNAL("activated()"), self.hello_world) - - # Add toolbar button and menu item - self.iface.addPluginToMenu("HelloWorld", self.action) - - def unload(self): - # Remove the plugin menu item and icon - self.iface.removePluginMenu("HelloWorld", self.action) - - # run - def hello_world(self): - QMessageBox.information( - self.iface.mainWindow(), - QCoreApplication.translate("HelloWorld", "HelloWorld"), - QCoreApplication.translate("HelloWorld", "HelloWorld"), - ) - return - - -if __name__ == "__main__": - pass diff --git a/qgis-app/plugins/tests/HelloWorld/1.9-full-md-max_qgs_version/HelloWorld/__init__.py b/qgis-app/plugins/tests/HelloWorld/1.9-full-md-max_qgs_version/HelloWorld/__init__.py deleted file mode 100644 index eff9426a..00000000 --- a/qgis-app/plugins/tests/HelloWorld/1.9-full-md-max_qgs_version/HelloWorld/__init__.py +++ /dev/null @@ -1,10 +0,0 @@ -# -*- coding: utf-8 -*- -""" - This script initializes the plugin, making it known to QGIS. -""" - - -def classFactory(iface): - from HelloWorld import HelloWorld - - return HelloWorld(iface) diff --git a/qgis-app/plugins/tests/HelloWorld/1.9-full-md-max_qgs_version/HelloWorld/icons/icon.png b/qgis-app/plugins/tests/HelloWorld/1.9-full-md-max_qgs_version/HelloWorld/icons/icon.png deleted file mode 100644 index 681cf0cb..00000000 Binary files a/qgis-app/plugins/tests/HelloWorld/1.9-full-md-max_qgs_version/HelloWorld/icons/icon.png and /dev/null differ diff --git a/qgis-app/plugins/tests/HelloWorld/1.9-full-md-max_qgs_version/HelloWorld/metadata.txt b/qgis-app/plugins/tests/HelloWorld/1.9-full-md-max_qgs_version/HelloWorld/metadata.txt deleted file mode 100644 index 7d075e12..00000000 --- a/qgis-app/plugins/tests/HelloWorld/1.9-full-md-max_qgs_version/HelloWorld/metadata.txt +++ /dev/null @@ -1,32 +0,0 @@ -[general] -name=HelloWorld -qgisMinimumVersion=1.8 -qgisMaximumVersion=2.1 -description=This is a test plugin for greeting the - (going multiline) world, version is 1.9 -version=version 1.9 -author=Alessandro Pasotti èàò -email=apasotti@gmail.com - - -changelog=this is a very - very
- very - very - very - very long multiline changelog - - -tags= wkt, raster,hello world, spaced tag , tag - -tracker=http://bugs.itopen.it -homepage=http://www.itopen.it -repository=http://www.itopen.it/repo - - - -experimental=False - - -; change icon... -icon=icons/icon.png diff --git a/qgis-app/plugins/tests/HelloWorld/2.0-full-md-empty-max_qgs_version/HelloWorld/HelloWorld.py b/qgis-app/plugins/tests/HelloWorld/2.0-full-md-empty-max_qgs_version/HelloWorld/HelloWorld.py deleted file mode 100644 index 45bdac04..00000000 --- a/qgis-app/plugins/tests/HelloWorld/2.0-full-md-empty-max_qgs_version/HelloWorld/HelloWorld.py +++ /dev/null @@ -1,40 +0,0 @@ -# -*- coding: utf-8 -*- -# Import the PyQt and QGIS libraries -from PyQt4.QtCore import * -from PyQt4.QtGui import * -from qgis.core import * - - -class HelloWorld: - def __init__(self, iface): - # Save reference to the QGIS interface - self.iface = iface - self.canvas = iface.mapCanvas() - - def initGui(self): - # Create action that will start plugin - self.action = QAction( - QIcon(":/plugins/"), "&HelloWorld", self.iface.mainWindow() - ) - # connect the action to the run method - QObject.connect(self.action, SIGNAL("activated()"), self.hello_world) - - # Add toolbar button and menu item - self.iface.addPluginToMenu("HelloWorld", self.action) - - def unload(self): - # Remove the plugin menu item and icon - self.iface.removePluginMenu("HelloWorld", self.action) - - # run - def hello_world(self): - QMessageBox.information( - self.iface.mainWindow(), - QCoreApplication.translate("HelloWorld", "HelloWorld"), - QCoreApplication.translate("HelloWorld", "HelloWorld"), - ) - return - - -if __name__ == "__main__": - pass diff --git a/qgis-app/plugins/tests/HelloWorld/2.0-full-md-empty-max_qgs_version/HelloWorld/__init__.py b/qgis-app/plugins/tests/HelloWorld/2.0-full-md-empty-max_qgs_version/HelloWorld/__init__.py deleted file mode 100644 index eff9426a..00000000 --- a/qgis-app/plugins/tests/HelloWorld/2.0-full-md-empty-max_qgs_version/HelloWorld/__init__.py +++ /dev/null @@ -1,10 +0,0 @@ -# -*- coding: utf-8 -*- -""" - This script initializes the plugin, making it known to QGIS. -""" - - -def classFactory(iface): - from HelloWorld import HelloWorld - - return HelloWorld(iface) diff --git a/qgis-app/plugins/tests/HelloWorld/2.0-full-md-empty-max_qgs_version/HelloWorld/icons/icon.png b/qgis-app/plugins/tests/HelloWorld/2.0-full-md-empty-max_qgs_version/HelloWorld/icons/icon.png deleted file mode 100644 index 681cf0cb..00000000 Binary files a/qgis-app/plugins/tests/HelloWorld/2.0-full-md-empty-max_qgs_version/HelloWorld/icons/icon.png and /dev/null differ diff --git a/qgis-app/plugins/tests/HelloWorld/2.0-full-md-empty-max_qgs_version/HelloWorld/metadata.txt b/qgis-app/plugins/tests/HelloWorld/2.0-full-md-empty-max_qgs_version/HelloWorld/metadata.txt deleted file mode 100644 index 19e50f4d..00000000 --- a/qgis-app/plugins/tests/HelloWorld/2.0-full-md-empty-max_qgs_version/HelloWorld/metadata.txt +++ /dev/null @@ -1,31 +0,0 @@ -[general] -name=HelloWorld -qgisMinimumVersion=2.0 -description=This is a test plugin for greeting the - (going multiline) world, version is 2.0 -version=version 2.0 -author=Alessandro Pasotti èàò -email=apasotti@gmail.com - - -changelog=this is a very - very
- very - very - very - very long multiline changelog - - -tags= wkt, raster,hello world, spaced tag , tag - -tracker=http://bugs.itopen.it -homepage=http://www.itopen.it -repository=http://www.itopen.it/repo - - - -experimental=False - - -; change icon... -icon=icons/icon.png diff --git a/qgis-app/plugins/tests/HelloWorld/2.1-full-md-max_qgs_version_2.999/HelloWorld/HelloWorld.py b/qgis-app/plugins/tests/HelloWorld/2.1-full-md-max_qgs_version_2.999/HelloWorld/HelloWorld.py deleted file mode 100644 index 45bdac04..00000000 --- a/qgis-app/plugins/tests/HelloWorld/2.1-full-md-max_qgs_version_2.999/HelloWorld/HelloWorld.py +++ /dev/null @@ -1,40 +0,0 @@ -# -*- coding: utf-8 -*- -# Import the PyQt and QGIS libraries -from PyQt4.QtCore import * -from PyQt4.QtGui import * -from qgis.core import * - - -class HelloWorld: - def __init__(self, iface): - # Save reference to the QGIS interface - self.iface = iface - self.canvas = iface.mapCanvas() - - def initGui(self): - # Create action that will start plugin - self.action = QAction( - QIcon(":/plugins/"), "&HelloWorld", self.iface.mainWindow() - ) - # connect the action to the run method - QObject.connect(self.action, SIGNAL("activated()"), self.hello_world) - - # Add toolbar button and menu item - self.iface.addPluginToMenu("HelloWorld", self.action) - - def unload(self): - # Remove the plugin menu item and icon - self.iface.removePluginMenu("HelloWorld", self.action) - - # run - def hello_world(self): - QMessageBox.information( - self.iface.mainWindow(), - QCoreApplication.translate("HelloWorld", "HelloWorld"), - QCoreApplication.translate("HelloWorld", "HelloWorld"), - ) - return - - -if __name__ == "__main__": - pass diff --git a/qgis-app/plugins/tests/HelloWorld/2.1-full-md-max_qgs_version_2.999/HelloWorld/__init__.py b/qgis-app/plugins/tests/HelloWorld/2.1-full-md-max_qgs_version_2.999/HelloWorld/__init__.py deleted file mode 100644 index eff9426a..00000000 --- a/qgis-app/plugins/tests/HelloWorld/2.1-full-md-max_qgs_version_2.999/HelloWorld/__init__.py +++ /dev/null @@ -1,10 +0,0 @@ -# -*- coding: utf-8 -*- -""" - This script initializes the plugin, making it known to QGIS. -""" - - -def classFactory(iface): - from HelloWorld import HelloWorld - - return HelloWorld(iface) diff --git a/qgis-app/plugins/tests/HelloWorld/2.1-full-md-max_qgs_version_2.999/HelloWorld/icons/icon.png b/qgis-app/plugins/tests/HelloWorld/2.1-full-md-max_qgs_version_2.999/HelloWorld/icons/icon.png deleted file mode 100644 index 681cf0cb..00000000 Binary files a/qgis-app/plugins/tests/HelloWorld/2.1-full-md-max_qgs_version_2.999/HelloWorld/icons/icon.png and /dev/null differ diff --git a/qgis-app/plugins/tests/HelloWorld/2.1-full-md-max_qgs_version_2.999/HelloWorld/metadata.txt b/qgis-app/plugins/tests/HelloWorld/2.1-full-md-max_qgs_version_2.999/HelloWorld/metadata.txt deleted file mode 100644 index 294e6d6b..00000000 --- a/qgis-app/plugins/tests/HelloWorld/2.1-full-md-max_qgs_version_2.999/HelloWorld/metadata.txt +++ /dev/null @@ -1,32 +0,0 @@ -[general] -name=HelloWorld -qgisMinimumVersion=1.8 -qgisMaximumVersion=2.999 -description=This is a test plugin for greeting the - (going multiline) world, version is 2.0 -version=version 2.1 -author=Alessandro Pasotti èàò -email=apasotti@gmail.com - - -changelog=this is a very - very
- very - very - very - very long multiline changelog - - -tags= wkt, raster,hello world, spaced tag , tag - -tracker=http://bugs.itopen.it -homepage=http://www.itopen.it -repository=http://www.itopen.it/repo - - - -experimental=False - - -; change icon... -icon=icons/icon.png diff --git a/qgis-app/plugins/tests/HelloWorld/2.2-full-md-empty-max_qgs_category_web/HelloWorld/HelloWorld.py b/qgis-app/plugins/tests/HelloWorld/2.2-full-md-empty-max_qgs_category_web/HelloWorld/HelloWorld.py deleted file mode 100644 index 45bdac04..00000000 --- a/qgis-app/plugins/tests/HelloWorld/2.2-full-md-empty-max_qgs_category_web/HelloWorld/HelloWorld.py +++ /dev/null @@ -1,40 +0,0 @@ -# -*- coding: utf-8 -*- -# Import the PyQt and QGIS libraries -from PyQt4.QtCore import * -from PyQt4.QtGui import * -from qgis.core import * - - -class HelloWorld: - def __init__(self, iface): - # Save reference to the QGIS interface - self.iface = iface - self.canvas = iface.mapCanvas() - - def initGui(self): - # Create action that will start plugin - self.action = QAction( - QIcon(":/plugins/"), "&HelloWorld", self.iface.mainWindow() - ) - # connect the action to the run method - QObject.connect(self.action, SIGNAL("activated()"), self.hello_world) - - # Add toolbar button and menu item - self.iface.addPluginToMenu("HelloWorld", self.action) - - def unload(self): - # Remove the plugin menu item and icon - self.iface.removePluginMenu("HelloWorld", self.action) - - # run - def hello_world(self): - QMessageBox.information( - self.iface.mainWindow(), - QCoreApplication.translate("HelloWorld", "HelloWorld"), - QCoreApplication.translate("HelloWorld", "HelloWorld"), - ) - return - - -if __name__ == "__main__": - pass diff --git a/qgis-app/plugins/tests/HelloWorld/2.2-full-md-empty-max_qgs_category_web/HelloWorld/__init__.py b/qgis-app/plugins/tests/HelloWorld/2.2-full-md-empty-max_qgs_category_web/HelloWorld/__init__.py deleted file mode 100644 index eff9426a..00000000 --- a/qgis-app/plugins/tests/HelloWorld/2.2-full-md-empty-max_qgs_category_web/HelloWorld/__init__.py +++ /dev/null @@ -1,10 +0,0 @@ -# -*- coding: utf-8 -*- -""" - This script initializes the plugin, making it known to QGIS. -""" - - -def classFactory(iface): - from HelloWorld import HelloWorld - - return HelloWorld(iface) diff --git a/qgis-app/plugins/tests/HelloWorld/2.2-full-md-empty-max_qgs_category_web/HelloWorld/icons/icon.png b/qgis-app/plugins/tests/HelloWorld/2.2-full-md-empty-max_qgs_category_web/HelloWorld/icons/icon.png deleted file mode 100644 index 681cf0cb..00000000 Binary files a/qgis-app/plugins/tests/HelloWorld/2.2-full-md-empty-max_qgs_category_web/HelloWorld/icons/icon.png and /dev/null differ diff --git a/qgis-app/plugins/tests/HelloWorld/2.2-full-md-empty-max_qgs_category_web/HelloWorld/metadata.txt b/qgis-app/plugins/tests/HelloWorld/2.2-full-md-empty-max_qgs_category_web/HelloWorld/metadata.txt deleted file mode 100644 index 843427ff..00000000 --- a/qgis-app/plugins/tests/HelloWorld/2.2-full-md-empty-max_qgs_category_web/HelloWorld/metadata.txt +++ /dev/null @@ -1,32 +0,0 @@ -[general] -name=HelloWorld -qgisMinimumVersion=1.8 -qgisMaximumVersion=2.99 -description=This is a test plugin for greeting the - (going multiline) world, version is 2.2 -version=version 2.2 -author=Alessandro Pasotti èàò -email=apasotti@gmail.com - - -changelog=this is a very - very
- very - very - very - very long multiline changelog - - -tags= wkt, raster,hello world, spaced tag , tag - -tracker=http://bugs.itopen.it -homepage=http://www.itopen.it -repository=http://www.itopen.it/repo - -category=web - -experimental=False - - -; change icon... -icon=icons/icon.png diff --git a/qgis-app/plugins/tests/HelloWorld/2.3-full-changed-repository/HelloWorld/HelloWorld.py b/qgis-app/plugins/tests/HelloWorld/2.3-full-changed-repository/HelloWorld/HelloWorld.py deleted file mode 100644 index 45bdac04..00000000 --- a/qgis-app/plugins/tests/HelloWorld/2.3-full-changed-repository/HelloWorld/HelloWorld.py +++ /dev/null @@ -1,40 +0,0 @@ -# -*- coding: utf-8 -*- -# Import the PyQt and QGIS libraries -from PyQt4.QtCore import * -from PyQt4.QtGui import * -from qgis.core import * - - -class HelloWorld: - def __init__(self, iface): - # Save reference to the QGIS interface - self.iface = iface - self.canvas = iface.mapCanvas() - - def initGui(self): - # Create action that will start plugin - self.action = QAction( - QIcon(":/plugins/"), "&HelloWorld", self.iface.mainWindow() - ) - # connect the action to the run method - QObject.connect(self.action, SIGNAL("activated()"), self.hello_world) - - # Add toolbar button and menu item - self.iface.addPluginToMenu("HelloWorld", self.action) - - def unload(self): - # Remove the plugin menu item and icon - self.iface.removePluginMenu("HelloWorld", self.action) - - # run - def hello_world(self): - QMessageBox.information( - self.iface.mainWindow(), - QCoreApplication.translate("HelloWorld", "HelloWorld"), - QCoreApplication.translate("HelloWorld", "HelloWorld"), - ) - return - - -if __name__ == "__main__": - pass diff --git a/qgis-app/plugins/tests/HelloWorld/2.3-full-changed-repository/HelloWorld/__init__.py b/qgis-app/plugins/tests/HelloWorld/2.3-full-changed-repository/HelloWorld/__init__.py deleted file mode 100644 index eff9426a..00000000 --- a/qgis-app/plugins/tests/HelloWorld/2.3-full-changed-repository/HelloWorld/__init__.py +++ /dev/null @@ -1,10 +0,0 @@ -# -*- coding: utf-8 -*- -""" - This script initializes the plugin, making it known to QGIS. -""" - - -def classFactory(iface): - from HelloWorld import HelloWorld - - return HelloWorld(iface) diff --git a/qgis-app/plugins/tests/HelloWorld/2.3-full-changed-repository/HelloWorld/icons/icon.png b/qgis-app/plugins/tests/HelloWorld/2.3-full-changed-repository/HelloWorld/icons/icon.png deleted file mode 100644 index 681cf0cb..00000000 Binary files a/qgis-app/plugins/tests/HelloWorld/2.3-full-changed-repository/HelloWorld/icons/icon.png and /dev/null differ diff --git a/qgis-app/plugins/tests/HelloWorld/2.3-full-changed-repository/HelloWorld/metadata.txt b/qgis-app/plugins/tests/HelloWorld/2.3-full-changed-repository/HelloWorld/metadata.txt deleted file mode 100644 index 3e885eb9..00000000 --- a/qgis-app/plugins/tests/HelloWorld/2.3-full-changed-repository/HelloWorld/metadata.txt +++ /dev/null @@ -1,34 +0,0 @@ -[general] -name=HelloWorld -qgisMinimumVersion=1.8 -qgisMaximumVersion=2.99 -description=This is a test plugin for greeting the - (going multiline) world, version is 2.3 -version=version 2.3 -author=Alessandro Pasotti èàò -email=apasotti@gmail.com - - -changelog=this is a very - very
- very - very - very - very long multiline changelog - - -tags= wkt, raster,hello world, spaced tag , tag - -tracker=http://bugs.itopen.it/changed -homepage=http://www.itopen.it/changed -repository=http://www.itopen.it/changedrepo - -category=web - -experimental=False - -about=Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. - - -; change icon... -icon=icons/icon.png diff --git a/qgis-app/plugins/tests/HelloWorld/Makefile b/qgis-app/plugins/tests/HelloWorld/Makefile deleted file mode 100644 index 6c425d8f..00000000 --- a/qgis-app/plugins/tests/HelloWorld/Makefile +++ /dev/null @@ -1,3 +0,0 @@ -all: - @if [ find -name HelloWorld_*.zip ]; then rm HelloWorld_*.zip; fi - @for i in $$(find . -maxdepth 1 -type d|egrep './.+'|sed -e 's/.\///' ); do cd $$i && zip -r HelloWorld_$$i.zip * && mv *.zip .. && cd ..; done; diff --git a/qgis-app/plugins/tests/HelloWorld/README b/qgis-app/plugins/tests/HelloWorld/README deleted file mode 100644 index c5d6f28a..00000000 --- a/qgis-app/plugins/tests/HelloWorld/README +++ /dev/null @@ -1,13 +0,0 @@ -Test plugins. - -Some can contain errors: - -1.0_spaced (space in folder name) -1.2_md_txt_incomplete (metadata.txt with missing fields) -1.2_wierdname to test for package name whoos search -1.8_author_slashes_error (slashes in author name: invalid) -1.9_full-md-max_qgs_version test max qgs version -2.0_full-md-empty-max_qgs_version test max qgs version empty -2.1_full-md-empty-max_qgs_version_2.999 test max qgs version -2.2_full-md-empty-max_qgs_category_web test category metadata -2.3_full-changed-repository test repository changed, also tests about metadata diff --git a/qgis-app/plugins/tests/__init__.py b/qgis-app/plugins/tests/__init__.py deleted file mode 100644 index 53d131f4..00000000 --- a/qgis-app/plugins/tests/__init__.py +++ /dev/null @@ -1,5 +0,0 @@ -from plugins.tests import ws_test - -__test__ = { - "ws_test": ws_test, -} diff --git a/qgis-app/plugins/tests/test_change_maintainer.py b/qgis-app/plugins/tests/test_change_maintainer.py deleted file mode 100644 index 515b5c46..00000000 --- a/qgis-app/plugins/tests/test_change_maintainer.py +++ /dev/null @@ -1,101 +0,0 @@ -import os -from unittest.mock import patch - -from django.urls import reverse -from django.test import Client, TestCase, override_settings -from django.contrib.auth.models import User -from django.core.files.uploadedfile import SimpleUploadedFile -from plugins.models import Plugin, PluginVersion -from plugins.forms import PluginForm - -def do_nothing(*args, **kwargs): - pass - -TESTFILE_DIR = os.path.abspath(os.path.join(os.path.dirname(__file__), "testfiles")) - -class PluginRenameTestCase(TestCase): - fixtures = [ - "fixtures/styles.json", - "fixtures/auth.json", - "fixtures/simplemenu.json", - ] - - @override_settings(MEDIA_ROOT="api/tests") - def setUp(self): - self.client = Client() - self.url_upload = reverse('plugin_upload') - - # Create a test user - self.user = User.objects.create_user( - username='testuser', - password='testpassword', - email='test@example.com' - ) - - # Log in the test user - self.client.login(username='testuser', password='testpassword') - - # Upload a plugin for renaming test. - # This process is already tested in test_plugin_upload - valid_plugin = os.path.join(TESTFILE_DIR, "valid_plugin.zip_") - with open(valid_plugin, "rb") as file: - uploaded_file = SimpleUploadedFile( - "valid_plugin.zip_", file.read(), - content_type="application/zip") - - self.client.post(self.url_upload, { - 'package': uploaded_file, - }) - - self.plugin = Plugin.objects.get(name='Test Plugin') - self.plugin.save() - - @patch("plugins.tasks.generate_plugins_xml", new=do_nothing) - @patch("plugins.validator._check_url_link", new=do_nothing) - def test_change_maintainer(self): - """ - Test change maintainer for plugin update - """ - package_name = self.plugin.package_name - self.url_plugin_update = reverse('plugin_update', args=[package_name]) - self.url_add_version = reverse('version_create', args=[package_name]) - - # Test GET request - response = self.client.get(self.url_plugin_update) - self.assertEqual(response.status_code, 200) - self.assertIsInstance(response.context['form'], PluginForm) - self.assertEqual(response.context['form']['maintainer'].value(), self.user.pk) - - - # Test POST request to change maintainer - - response = self.client.post(self.url_plugin_update, { - 'description': self.plugin.description, - 'about': self.plugin.about, - 'author': self.plugin.author, - 'email': self.plugin.email, - 'tracker': self.plugin.tracker, - 'repository': self.plugin.repository, - 'maintainer': 1, - }) - self.assertEqual(response.status_code, 302) - self.assertEqual(Plugin.objects.get(name='Test Plugin').maintainer.pk, 1) - - # Test POST request with new version - - valid_plugin = os.path.join(TESTFILE_DIR, "valid_plugin_0.0.2.zip_") - with open(valid_plugin, "rb") as file: - uploaded_file = SimpleUploadedFile( - "valid_plugin_0.0.2.zip_", file.read(), - content_type="application/zip_") - - response = self.client.post(self.url_add_version, { - 'package': uploaded_file, - 'experimental': False, - 'changelog': '' - }) - self.assertEqual(response.status_code, 302) - self.assertEqual(Plugin.objects.get(name='Test Plugin').maintainer.pk, 1) - - def tearDown(self): - self.client.logout() diff --git a/qgis-app/plugins/tests/test_download.py b/qgis-app/plugins/tests/test_download.py deleted file mode 100644 index 6ebc097a..00000000 --- a/qgis-app/plugins/tests/test_download.py +++ /dev/null @@ -1,85 +0,0 @@ -from django.test import Client, TestCase, RequestFactory -from django.contrib.auth.models import User -from django.utils import timezone -from django.core.files.uploadedfile import SimpleUploadedFile - -from plugins.models import Plugin, PluginVersion, PluginVersionDownload -from plugins.views import version_download -from django.urls import reverse - -class TestVersionDownloadView(TestCase): - def setUp(self): - self.factory = RequestFactory() - - self.user = User.objects.create_user( - username='testuser', - password='12345' - ) - - self.plugin = Plugin.objects.create( - package_name="test-package", - created_by=self.user, - ) - - self.version = PluginVersion.objects.create( - plugin=self.plugin, - version="1.0.0", - downloads=0, - created_by=self.user, - package=SimpleUploadedFile("test.zip", b"file_content"), - min_qg_version='3.1.1', - max_qg_version='3.3.0' - ) - - def test_version_download(self): - request = self.factory.get('/') - - response = version_download(request, self.plugin.package_name, self.version.version) - - self.version.refresh_from_db() - self.plugin.refresh_from_db() - download_record = PluginVersionDownload.objects.get( - plugin_version=self.version, - download_date=timezone.now().date() - ) - - self.assertEqual(response.status_code, 200) - self.assertEqual(response['Content-Type'], 'application/zip') - self.assertEqual(response.content, b'file_content') - - self.assertEqual(self.version.downloads, 1) - self.assertEqual(self.plugin.downloads, 1) - self.assertEqual(download_record.download_count, 1) - - def test_version_download_per_country(self): - download_url = reverse('version_download', args=[self.plugin.package_name, self.version.version]) - c = Client(REMOTE_ADDR='180.247.213.170') - response = c.get(download_url) - - self.version.refresh_from_db() - self.plugin.refresh_from_db() - download_record = PluginVersionDownload.objects.get( - plugin_version=self.version, - download_date=timezone.now().date() - ) - - self.assertEqual(response.status_code, 200) - self.assertTrue(download_record.country_code == 'ID') - self.assertTrue(download_record.country_name == 'Indonesia') - - - def test_download_per_country_with_invalid_ip(self): - download_url = reverse('version_download', args=[self.plugin.package_name, self.version.version]) - c = Client(REMOTE_ADDR='123.456.789.100') - response = c.get(download_url) - - self.version.refresh_from_db() - self.plugin.refresh_from_db() - download_record = PluginVersionDownload.objects.get( - plugin_version=self.version, - download_date=timezone.now().date() - ) - - self.assertEqual(response.status_code, 200) - self.assertTrue(download_record.country_code == 'N/D') - self.assertTrue(download_record.country_name == 'N/D') \ No newline at end of file diff --git a/qgis-app/plugins/tests/test_filter_template.py b/qgis-app/plugins/tests/test_filter_template.py deleted file mode 100644 index 8b2419f0..00000000 --- a/qgis-app/plugins/tests/test_filter_template.py +++ /dev/null @@ -1,56 +0,0 @@ -from datetime import datetime - -import pytz -from django.contrib.auth.models import User -from django.test import TestCase -from django.urls import reverse -from plugins.models import Plugin, PluginVersion - - -class TestPluginFilterTemplate(TestCase): - fixtures = ["fixtures/simplemenu.json"] - - def setUp(self) -> None: - self.creator = User.objects.create( - username="creator", email="creator@email.com" - ) - # set creator password to password - self.creator.set_password("password") - self.creator.save() - self.plugin_name = "plugin_name_test" - self.plugin = Plugin.objects.create( - created_by=self.creator, - name=self.plugin_name, - package_name=self.plugin_name, - ) - self.version = PluginVersion.objects.create( - plugin=self.plugin, - created_by=self.creator, - version="1.1.0", - min_qg_version="0.0.1", - max_qg_version="2.2.0", - ) - self.created_on = datetime(2022, 1, 1, 1, 0, 0) - self.version.created_on = self.created_on - self.version.save() - - def tearDown(self) -> None: - self.plugin.delete() - self.creator.delete() - self.version.delete() - - def test_detail_plugin_version_tab_displaying_local_timezone(self): - url = reverse( - "plugin_detail", kwargs={"package_name": self.plugin.package_name} - ) - response = self.client.get(url) - self.assertEqual(response.status_code, 200) - self.assertTrue( - bytes( - '{}'.format( - self.created_on.astimezone(pytz.utc).isoformat() - ), - "utf-8", - ) - in response.content - ) diff --git a/qgis-app/plugins/tests/test_models.py b/qgis-app/plugins/tests/test_models.py deleted file mode 100644 index 4c0a26c6..00000000 --- a/qgis-app/plugins/tests/test_models.py +++ /dev/null @@ -1,129 +0,0 @@ -from datetime import datetime - -from freezegun import freeze_time - -from django.contrib.auth.models import User -from django.test import TestCase -from plugins.models import Plugin, PluginVersion, PluginVersionFeedback - - -class PluginVersionFeedbackTest(TestCase): - fixtures = ["fixtures/auth.json", ] - - def setUp(self): - self.creator = User.objects.get(id=2) - self.admin = User.objects.get(id=1) - self.staff = User.objects.get(id=3) - self.plugin = Plugin.objects.create( - created_by=self.creator, - repository="http://example.com", - tracker="http://example.com", - package_name="test-feedback", - name="test feedback", - about="this is a test for plugin feedbacks" - ) - self.version = PluginVersion.objects.create( - plugin=self.plugin, - created_by=self.creator, - min_qg_version="0.0.0", - max_qg_version="99.99.99", - version="0.2", - approved=False, - external_deps="test" - ) - - def test_create_feedback_success(self): - feedback = PluginVersionFeedback.objects.create( - version=self.version, - reviewer=self.staff, - task="test comment in a feedback." - ) - self.assertIsNotNone(feedback.created_on) - self.assertFalse(feedback.is_completed) - self.assertIsNone(feedback.completed_on) - - @freeze_time("2023-06-30 10:00:00") - def test_update_feedback_is_completed(self): - feedback = PluginVersionFeedback.objects.create( - version=self.version, - reviewer=self.staff, - task="test comment in a feedback.", - is_completed=True - ) - self.assertEqual(feedback.completed_on, datetime(2023, 6, 30, 10, 0, 0)) - feedback.is_completed = False - feedback.save() - self.assertIsNone(feedback.completed_on) - - -class PluginVersionFeedbackManagerTest(TestCase): - fixtures = ["fixtures/auth.json", ] - - def setUp(self): - self.creator = User.objects.get(id=2) - self.staff = User.objects.get(id=3) - self.plugin_1 = Plugin.objects.create( - created_by=self.creator, - repository="http://example.com", - tracker="http://example.com", - package_name="plugin-test-1", - name="plugin test 1", - about="this is a test for plugin feedbacks" - ) - self.version_1 = PluginVersion.objects.create( - plugin=self.plugin_1, - created_by=self.creator, - min_qg_version="0.0.0", - max_qg_version="99.99.99", - version="1.0", - approved=False, - external_deps="test" - ) - self.feedback_1 = PluginVersionFeedback.objects.create( - version=self.version_1, - reviewer=self.staff, - task="test comment in a feedback." - ) - self.plugin_2 = Plugin.objects.create( - created_by=self.creator, - repository="http://example.com", - tracker="http://example.com", - package_name="plugin-test-2", - name="plugin test 2", - about="this is a test for plugin feedbacks" - ) - self.version_2 = PluginVersion.objects.create( - plugin=self.plugin_2, - created_by=self.creator, - min_qg_version="0.0.0", - max_qg_version="99.99.99", - version="2.0", - approved=False, - external_deps="test" - ) - - def test_query_plugins_objects_all(self): - plugins = Plugin.objects.all() - self.assertEqual(len(plugins), 2) - - def test_query_plugins_feedback_received_objects(self): - plugins = Plugin.feedback_received_objects.all() - self.assertEqual(len(plugins), 1) - self.assertEqual(plugins[0], self.plugin_1) - - PluginVersionFeedback.objects.create( - version=self.version_2, - reviewer=self.staff, - task="test comment in a feedback for plugin 2." - ) - plugins = Plugin.feedback_received_objects.all() - self.assertEqual(len(plugins), 2) - self.assertListEqual(list(plugins), [self.plugin_1, self.plugin_2]) - - - def test_query_plugins_feedback_pending_objects(self): - plugins = Plugin.feedback_pending_objects.all() - self.assertEqual(len(plugins), 1) - self.assertEqual(plugins[0], self.plugin_2) - - diff --git a/qgis-app/plugins/tests/test_plugin_list.py b/qgis-app/plugins/tests/test_plugin_list.py deleted file mode 100644 index 48972e11..00000000 --- a/qgis-app/plugins/tests/test_plugin_list.py +++ /dev/null @@ -1,57 +0,0 @@ -from django.test import TestCase -from django.urls import reverse -from plugins.models import Plugin - -class PluginsListViewTestCase(TestCase): - fixtures = [ - "fixtures/styles.json", - "fixtures/auth.json", - "fixtures/simplemenu.json", - "fixtures/plugins.json", - ] - - def setUp(self): - pass - - def test_plugins_list_view(self): - # Test the main plugins list view without any parameters - response = self.client.get(reverse('approved_plugins')) - self.assertEqual(response.status_code, 200) - self.assertTemplateUsed(response, 'plugins/plugin_list.html') - self.assertTrue('current_sort_query' in response.context) - self.assertTrue('current_querystring' in response.context) - self.assertTrue('per_page_list' in response.context) - self.assertTrue('show_more_items_number' in response.context) - - def test_plugins_list_pagination(self): - # Test the plugins list view with pagination - response = self.client.get(reverse('approved_plugins'), {'per_page': 20}) - self.assertEqual(response.status_code, 200) - self.assertTrue('current_sort_query' in response.context) - self.assertTrue('current_querystring' in response.context) - self.assertTrue('per_page_list' in response.context) - self.assertTrue('show_more_items_number' in response.context) - - show_more_items_number = response.context['show_more_items_number'] - self.assertEqual(show_more_items_number, 50) - - response = self.client.get(reverse('approved_plugins'), {'per_page': 110}) - self.assertEqual(response.status_code, 200) - self.assertTrue('current_sort_query' in response.context) - self.assertTrue('current_querystring' in response.context) - self.assertTrue('per_page_list' in response.context) - self.assertTrue('show_more_items_number' in response.context) - - show_more_items_number = response.context['show_more_items_number'] - records_count = Plugin.approved_objects.count() - self.assertEqual(show_more_items_number, records_count + 1) - - def test_plugins_list_sorting(self): - # Test the plugins list view with sorting - response = self.client.get(reverse('approved_plugins'), {'sort': 'name'}) - self.assertEqual(response.status_code, 200) - self.assertTrue('current_sort_query' in response.context) - self.assertTrue('current_querystring' in response.context) - self.assertTrue('per_page_list' in response.context) - self.assertTrue('show_more_items_number' in response.context) - diff --git a/qgis-app/plugins/tests/test_plugin_update.py b/qgis-app/plugins/tests/test_plugin_update.py deleted file mode 100644 index 118b12d8..00000000 --- a/qgis-app/plugins/tests/test_plugin_update.py +++ /dev/null @@ -1,169 +0,0 @@ -import os -from unittest.mock import patch - -from django.urls import reverse -from django.test import Client, TestCase, override_settings -from django.contrib.auth.models import User -from django.core.files.uploadedfile import SimpleUploadedFile -from plugins.models import Plugin, PluginVersion -from plugins.forms import PluginVersionForm -from django.core import mail -from django.conf import settings - -def do_nothing(*args, **kwargs): - pass - -TESTFILE_DIR = os.path.abspath(os.path.join(os.path.dirname(__file__), "testfiles")) - -class PluginUpdateTestCase(TestCase): - fixtures = [ - "fixtures/styles.json", - "fixtures/auth.json", - "fixtures/simplemenu.json", - ] - - @override_settings(MEDIA_ROOT="api/tests") - def setUp(self): - self.client = Client() - self.url_upload = reverse('plugin_upload') - - # Create a test user - self.user = User.objects.create_user( - username='testuser', - password='testpassword', - email='test@example.com' - ) - - # Log in the test user - self.client.login(username='testuser', password='testpassword') - - # Upload a plugin for renaming test. - # This process is already tested in test_plugin_upload - valid_plugin = os.path.join(TESTFILE_DIR, "valid_plugin.zip_") - with open(valid_plugin, "rb") as file: - uploaded_file = SimpleUploadedFile( - "valid_plugin.zip_", file.read(), - content_type="application/zip") - - self.client.post(self.url_upload, { - 'package': uploaded_file, - }) - - self.plugin = Plugin.objects.get(name='Test Plugin') - - @patch("plugins.tasks.generate_plugins_xml", new=do_nothing) - @patch("plugins.validator._check_url_link", new=do_nothing) - def test_plugin_new_version(self): - """ - Test upload a new plugin version with a modified metadata - """ - package_name = self.plugin.package_name - self.assertEqual(self.plugin.homepage, "https://example.net/") - self.assertEqual(self.plugin.tracker, "https://example.net/") - self.assertEqual(self.plugin.repository, "https://example.net/") - self.url_add_version = reverse('version_create', args=[package_name]) - - # Test POST request without allowing name from metadata - valid_plugin = os.path.join(TESTFILE_DIR, "change_metadata.zip_") - with open(valid_plugin, "rb") as file: - uploaded_file = SimpleUploadedFile( - "change_metadata.zip_", file.read(), - content_type="application/zip_") - - response = self.client.post(self.url_add_version, { - 'package': uploaded_file, - 'experimental': False, - 'changelog': '' - }) - self.assertEqual(response.status_code, 302) - - # The old version should always exist when creating a new version - self.assertTrue(PluginVersion.objects.filter( - plugin__name='Test Plugin', - version='0.0.1').exists() - ) - self.assertTrue(PluginVersion.objects.filter( - plugin__name='Test Plugin', - version='0.0.2').exists() - ) - - self.plugin = Plugin.objects.get(name='Test Plugin') - self.assertEqual(self.plugin.homepage, "https://github.com/") - self.assertEqual(self.plugin.tracker, "https://github.com/") - self.assertEqual(self.plugin.repository, "https://github.com/") - - self.assertIn( - 'admin@admin.it', - mail.outbox[0].recipients(), - ) - self.assertIn( - 'staff@staff.it', - mail.outbox[0].recipients() - ) - - # Should use the new email - self.assertEqual( - mail.outbox[0].from_email, - settings.EMAIL_HOST_USER - ) - - @patch("plugins.tasks.generate_plugins_xml", new=do_nothing) - @patch("plugins.validator._check_url_link", new=do_nothing) - def test_plugin_version_update(self): - """ - Test update a plugin version with a modified metadata - """ - package_name = self.plugin.package_name - self.assertEqual(self.plugin.homepage, "https://example.net/") - self.assertEqual(self.plugin.tracker, "https://example.net/") - self.assertEqual(self.plugin.repository, "https://example.net/") - self.url_add_version = reverse('version_update', args=[package_name, '0.0.1']) - - # Test POST request without allowing name from metadata - valid_plugin = os.path.join(TESTFILE_DIR, "change_metadata.zip_") - with open(valid_plugin, "rb") as file: - uploaded_file = SimpleUploadedFile( - "change_metadata.zip_", file.read(), - content_type="application/zip_") - - response = self.client.post(self.url_add_version, { - 'package': uploaded_file, - 'experimental': False, - 'changelog': '' - }) - self.assertEqual(response.status_code, 302) - - # The old version should not exist anymore - # TODO: The old version still exist, not sure why - # self.assertFalse(PluginVersion.objects.filter( - # plugin__name='Test Plugin', - # version='0.0.1').exists() - # ) - self.assertTrue(PluginVersion.objects.filter( - plugin__name='Test Plugin', - version='0.0.2').exists() - ) - - self.plugin = Plugin.objects.get(name='Test Plugin') - self.assertEqual(self.plugin.homepage, "https://github.com/") - self.assertEqual(self.plugin.tracker, "https://github.com/") - self.assertEqual(self.plugin.repository, "https://github.com/") - - self.assertIn( - 'admin@admin.it', - mail.outbox[0].recipients(), - ) - self.assertIn( - 'staff@staff.it', - mail.outbox[0].recipients() - ) - - # Should use the new email - self.assertEqual( - mail.outbox[0].from_email, - settings.EMAIL_HOST_USER - ) - - - def tearDown(self): - self.client.logout() diff --git a/qgis-app/plugins/tests/test_plugin_upload.py b/qgis-app/plugins/tests/test_plugin_upload.py deleted file mode 100644 index f2819ea9..00000000 --- a/qgis-app/plugins/tests/test_plugin_upload.py +++ /dev/null @@ -1,87 +0,0 @@ -import os -from unittest.mock import patch - -from django.urls import reverse -from django.test import Client, TestCase, override_settings -from django.contrib.auth.models import User -from django.core.files.uploadedfile import SimpleUploadedFile -from plugins.models import Plugin, PluginVersion -from plugins.forms import PackageUploadForm -from django.core import mail -from django.conf import settings - -def do_nothing(*args, **kwargs): - pass - -TESTFILE_DIR = os.path.abspath(os.path.join(os.path.dirname(__file__), "testfiles")) - -class PluginUploadTestCase(TestCase): - fixtures = [ - "fixtures/styles.json", - "fixtures/auth.json", - "fixtures/simplemenu.json", - ] - - @override_settings(MEDIA_ROOT="api/tests") - def setUp(self): - self.client = Client() - self.url = reverse('plugin_upload') - - # Create a test user - self.user = User.objects.create_user( - username='testuser', - password='testpassword', - email='test@example.com' - ) - - @patch("plugins.tasks.generate_plugins_xml", new=do_nothing) - @patch("plugins.validator._check_url_link", new=do_nothing) - def test_plugin_upload_form(self): - # Log in the test user - self.client.login(username='testuser', password='testpassword') - - # Test GET request - response = self.client.get(self.url) - self.assertEqual(response.status_code, 200) - self.assertIsInstance(response.context['form'], PackageUploadForm) - - # Test POST request with invalid form data - response = self.client.post(self.url, {}) - self.assertEqual(response.status_code, 200) - self.assertFalse(response.context['form'].is_valid()) - - valid_plugin = os.path.join(TESTFILE_DIR, "valid_plugin.zip_") - with open(valid_plugin, "rb") as file: - uploaded_file = SimpleUploadedFile( - "valid_plugin.zip_", file.read(), - content_type="application/zip") - - # Test POST request with valid form data - response = self.client.post(self.url, { - 'package': uploaded_file, - }) - - self.assertEqual(response.status_code, 302) - self.assertTrue(Plugin.objects.filter(name='Test Plugin').exists()) - self.assertEqual( - Plugin.objects.get(name='Test Plugin').tags.filter( - name__in=['python', 'example', 'test']).count(), - 3) - self.assertTrue(PluginVersion.objects.filter(plugin__name='Test Plugin', version='0.0.1').exists()) - - self.assertIn( - 'admin@admin.it', - mail.outbox[0].recipients(), - ) - self.assertIn( - 'staff@staff.it', - mail.outbox[0].recipients() - ) - - # Should use the new email - self.assertEqual( - mail.outbox[0].from_email, - settings.EMAIL_HOST_USER - ) - def tearDown(self): - self.client.logout() diff --git a/qgis-app/plugins/tests/test_plugin_version_feedback.py b/qgis-app/plugins/tests/test_plugin_version_feedback.py deleted file mode 100644 index 278454ac..00000000 --- a/qgis-app/plugins/tests/test_plugin_version_feedback.py +++ /dev/null @@ -1,502 +0,0 @@ -import datetime - -from django.contrib.auth.models import User -from django.core import mail -from django.test import TestCase -from django.urls import reverse - -from freezegun import freeze_time - -from plugins.models import Plugin, PluginVersion, PluginVersionFeedback -from plugins.views import version_feedback_notify -from django.conf import settings -from django.utils.dateformat import format -import json - -class SetupMixin: - fixtures = ["fixtures/auth.json", "fixtures/simplemenu.json"] - - def setUp(self) -> None: - self.creator = User.objects.get(id=2) - self.staff = User.objects.get(id=3) - self.plugin_1 = Plugin.objects.create( - created_by=self.creator, - repository="http://example.com", - tracker="http://example.com", - package_name="test-feedback", - name="test plugin 1", - about="this is a test for plugin feedbacks", - author="author plugin" - ) - self.version_1 = PluginVersion.objects.create( - plugin=self.plugin_1, - created_by=self.creator, - min_qg_version="0.0.0", - max_qg_version="99.99.99", - version="0.1", - approved=False, - external_deps="test" - ) - self.feedback_1 = PluginVersionFeedback.objects.create( - version=self.version_1, - reviewer=self.staff, - task="test comment in a feedback." - ) - self.plugin_2 = Plugin.objects.create( - created_by=self.creator, - repository="http://example.com", - tracker="http://example.com", - package_name="plugin-test-2", - name="test plugin 2", - about="this is a test for plugin feedbacks", - author="author plugin 2" - ) - self.version_2 = PluginVersion.objects.create( - plugin=self.plugin_2, - created_by=self.creator, - min_qg_version="0.0.0", - max_qg_version="99.99.99", - version="2.0", - approved=False, - external_deps="test" - ) - - -class TestFeedbackNotify(SetupMixin, TestCase): - def test_version_feedback_notify_no_email(self): - self.assertFalse(self.creator.email) - with self.assertLogs(level='WARNING'): - version_feedback_notify(self.version_1, self.creator) - - def test_version_feedback_notify_sent(self): - self.creator.email = 'email@example.com' - self.creator.save() - with self.assertLogs(level='DEBUG'): - version_feedback_notify(self.version_1, self.staff) - self.assertEqual(len(mail.outbox), 1) - self.assertEqual( - mail.outbox[0].subject, - f"Plugin {self.plugin_1} feedback notification." - ) - self.assertIn( - ( - "\nPlugin test plugin 1 reviewed by staff and received a " - "feedback.\nLink: http://example.com/plugins/test-feedback/" - "version/0.1/feedback/\n" - ), - mail.outbox[0].body - ) - self.assertEqual( - mail.outbox[0].recipients(), - ['email@example.com'] - ) - - # Should use the new email - self.assertEqual( - mail.outbox[0].from_email, - settings.EMAIL_HOST_USER - ) - - def test_add_recipient_in_email_notification(self): - self.creator.email = 'email@example.com' - self.creator.save() - new_recipient = User.objects.create( - username="new-recipient", - email="new@example.com" - ) - self.plugin_1.owners.add(new_recipient) - self.assertListEqual( - list(self.plugin_1.editors), - [new_recipient, self.creator] - ) - with self.assertLogs(level='DEBUG'): - version_feedback_notify(self.version_1, self.staff) - self.assertEqual( - mail.outbox[0].recipients(), - ['new@example.com', 'email@example.com'] - ) - - # Should use the new email - self.assertEqual( - mail.outbox[0].from_email, - settings.EMAIL_HOST_USER - ) - -class TestPluginFeedbackCompletedList(SetupMixin, TestCase): - fixtures = ["fixtures/simplemenu.json", "fixtures/auth.json"] - - def setUp(self): - super().setUp() - self.feedback_1.is_completed = True - self.feedback_1.save() - self.url = reverse("feedback_completed_plugins") - - def test_non_staff_should_not_see_plugin_feedback_completed_list(self): - response = self.client.get(self.url) - self.assertEqual(response.status_code, 404) - - self.client.force_login(user=self.creator) - response = self.client.get(self.url) - self.assertEqual(response.status_code, 404) - - def test_staff_should_see_plugin_feedback_completed(self): - self.client.force_login(user=self.staff) - response = self.client.get(self.url) - self.assertEqual(response.status_code, 200) - self.assertTemplateUsed( - response, 'plugins/plugin_list.html' - ) - self.assertEqual( - list(response.context['object_list']), - [self.plugin_1] - ) - self.assertContains(response, "test plugin 1") - self.assertNotContains(response, "test plugin 2") - - # add feedback for plugin 2 - PluginVersionFeedback.objects.create( - version=self.version_2, - reviewer=self.staff, - task="test comment in a feedback for plugin 2." - ) - response = self.client.get(self.url) - - # The plugin should not appear in the feedback completed list - self.assertEqual( - list(response.context['object_list']), - [self.plugin_1] - ) - self.assertNotContains(response, "test plugin 2") - - def test_approved_plugin_should_not_show_in_feedback_completed_list(self): - self.client.force_login(user=self.staff) - response = self.client.get(self.url) - self.assertEqual( - list(response.context['object_list']), - [self.plugin_1] - ) - self.version_1.approved = True - self.version_1.save() - response = self.client.get(self.url) - self.assertEqual( - list(response.context['object_list']), - [] - ) - -class TestPluginFeedbackReceivedList(SetupMixin, TestCase): - fixtures = ["fixtures/simplemenu.json", "fixtures/auth.json"] - - def setUp(self): - super().setUp() - self.url = reverse("feedback_received_plugins") - - def test_non_staff_should_not_see_plugin_feedback_received_list(self): - response = self.client.get(self.url) - self.assertEqual(response.status_code, 404) - - self.client.force_login(user=self.creator) - response = self.client.get(self.url) - self.assertEqual(response.status_code, 404) - - def test_staff_should_see_plugin_feedback_received(self): - self.client.force_login(user=self.staff) - response = self.client.get(self.url) - self.assertEqual(response.status_code, 200) - self.assertTemplateUsed( - response, 'plugins/plugin_list.html' - ) - self.assertEqual( - list(response.context['object_list']), - [self.plugin_1] - ) - self.assertContains(response, "test plugin 1") - self.assertNotContains(response, "test plugin 2") - - # add feedback for plugin 2 - PluginVersionFeedback.objects.create( - version=self.version_2, - reviewer=self.staff, - task="test comment in a feedback for plugin 2." - ) - response = self.client.get(self.url) - object_list = set(response.context['object_list']) - expected_objects = {self.plugin_1, self.plugin_2} - self.assertEqual(object_list, expected_objects) - self.assertContains(response, "test plugin 2") - - def test_approved_plugin_should_not_show_in_feedback_received_list(self): - self.client.force_login(user=self.staff) - response = self.client.get(self.url) - self.assertEqual( - list(response.context['object_list']), - [self.plugin_1] - ) - self.version_1.approved = True - self.version_1.save() - response = self.client.get(self.url) - self.assertEqual( - list(response.context['object_list']), - [] - ) - - -class TestPluginFeedbackPendingList(SetupMixin, TestCase): - fixtures = ["fixtures/simplemenu.json", "fixtures/auth.json"] - - def setUp(self): - super().setUp() - self.url = reverse("feedback_pending_plugins") - - def test_non_staff_should_not_see_plugin_feedback_pending_list(self): - response = self.client.get(self.url) - self.assertEqual(response.status_code, 404) - - self.client.force_login(user=self.creator) - response = self.client.get(self.url) - self.assertEqual(response.status_code, 404) - - def test_staff_should_see_plugin_feedback_pending_list(self): - self.client.force_login(user=self.staff) - response = self.client.get(self.url) - self.assertEqual(response.status_code, 200) - self.assertTemplateUsed( - response, 'plugins/plugin_list.html' - ) - self.assertEqual( - list(response.context['object_list']), - [self.plugin_2] - ) - self.assertContains(response, "test plugin 2") - self.assertNotContains(response, "test plugin 1") - - # add feedback for plugin 2 - PluginVersionFeedback.objects.create( - version=self.version_2, - reviewer=self.staff, - task="test comment in a feedback for plugin 2." - ) - response = self.client.get(self.url) - self.assertEqual( - list(response.context['object_list']), - [] - ) - - -class TestCreateVersionFeedback(SetupMixin, TestCase): - def setUp(self) -> None: - super().setUp() - self.url = reverse( - "version_feedback", - kwargs={ - "package_name": self.plugin_2.package_name, - "version": self.version_2.version - } - ) - self.new_user = User.objects.create( - username="new-user", - is_staff=False - ) - - def test_version_feedback_required_login(self): - response = self.client.get(self.url) - self.assertEqual(response.status_code, 302) - self.assertRedirects( - response, - f"/accounts/login/?next={self.url}" - ) - - def test_only_plugin_editor_and_staff_can_see_version_feedback_page(self): - self.client.force_login(self.new_user) - response = self.client.get(self.url) - self.assertEqual(response.status_code, 403) - self.client.force_login(user=self.staff) - response = self.client.get(self.url) - self.assertEqual(response.status_code, 200) - - def test_only_staff_can_see_new_feedback_form(self): - self.client.force_login(user=self.creator) - response = self.client.get(self.url) - self.assertEqual(response.status_code, 200) - self.assertNotContains(response, '
') - self.client.force_login(user=self.staff) - response = self.client.get(self.url) - self.assertEqual(response.status_code, 200) - self.assertContains(response, '
') - - def test_post_create_single_task_feedback(self): - self.client.force_login(self.staff) - response = self.client.post( - self.url, - data={ - "feedback": "single line feedback" - } - ) - self.assertEqual(response.status_code, 200) - feedbacks = PluginVersionFeedback.objects.filter( - version=self.version_2).all() - self.assertEqual(len(feedbacks), 1) - self.assertEqual(feedbacks[0].task, "single line feedback") - - def test_post_create_multiple_task_feedback(self): - self.client.force_login(self.staff) - response = self.client.post( - self.url, - data={ - "feedback": "- [ ] task one\n - [ ] task two" - } - ) - self.assertEqual(response.status_code, 200) - feedbacks = PluginVersionFeedback.objects.filter( - version=self.version_2).all() - self.assertEqual(len(feedbacks), 2) - self.assertEqual(feedbacks[0].task, "task one") - self.assertEqual(feedbacks[1].task, "task two") - - def test_post_create_invalid_bullet_point_1(self): - self.client.force_login(self.staff) - self.client.post( - self.url, - data={ - "feedback": "-[ ] invalid bullet point \n -[ ] invalid two" - } - ) - feedbacks = PluginVersionFeedback.objects.filter( - version=self.version_2).all() - self.assertEqual(len(feedbacks), 1) - self.assertEqual( - feedbacks[0].task, - "-[ ] invalid bullet point \n -[ ] invalid two" - ) - - def test_post_create_invalid_bullet_point_2(self): - self.client.force_login(self.staff) - self.client.post( - self.url, - data={ - "feedback": ("-[ ] invalid bullet point\n" - " - [ ] only save valid bullet point") - } - ) - feedbacks = PluginVersionFeedback.objects.filter( - version=self.version_2).all() - self.assertEqual(len(feedbacks), 1) - self.assertEqual(feedbacks[0].task, "only save valid bullet point") - - -class TestDeleteVersionFeedback(SetupMixin, TestCase): - def setUp(self) -> None: - super().setUp() - self.url = reverse( - "version_feedback_delete", - kwargs={ - "package_name": self.plugin_1.package_name, - "version": self.version_1.version, - "feedback": self.feedback_1.id - } - ) - - def test_only_the_reviewer_can_delete_a_feedback(self): - self.client.force_login(user=self.creator) - response = self.client.post( - self.url, - data={ - "status_feedback": "deleted" - } - ) - self.assertEqual(response.status_code, 200) - self.assertTrue(self.version_1.feedback.exists()) - self.client.force_login(user=self.staff) - response = self.client.post( - self.url, - data={ - "status_feedback": "deleted" - } - ) - - self.assertEqual(response.status_code, 200) - self.assertFalse(self.version_1.feedback.exists()) - - -class TestUpdateVersionFeedback(SetupMixin, TestCase): - def setUp(self) -> None: - super().setUp() - self.url = reverse( - "version_feedback_update", - kwargs={ - "package_name": self.plugin_1.package_name, - "version": self.version_1.version - } - ) - - @freeze_time("2023-06-30 10:00:00") - def test_staff_and_editor_can_update_feedback(self): - feedbacks = self.version_1.feedback.all() - self.assertEqual(len(feedbacks), 1) - self.assertFalse(feedbacks[0].is_completed) - self.client.force_login(user=self.creator) - response = self.client.post( - self.url, - data={ - "completed_tasks": [feedbacks[0].id] - } - ) - self.assertEqual(response.status_code, 201) - feedbacks = self.version_1.feedback.all() - self.assertEqual(len(feedbacks), 1) - self.assertTrue(feedbacks[0].is_completed) - self.assertEqual( - feedbacks[0].completed_on, datetime.datetime(2023, 6, 30, 10, 0, 0)) - - def test_non_staff_and_non_editor_cannot_update_feedback(self): - feedback = self.version_1.feedback.first() - new_user = User.objects.create(username="new-user") - self.client.force_login(user=new_user) - self.client.post( - self.url, - data={ - "status_feedback": [feedback.id] - } - ) - feedback = self.version_1.feedback.first() - self.assertFalse(feedback.is_completed) - self.assertIsNone(feedback.completed_on) - -class VersionFeedbackEditViewTests(SetupMixin, TestCase): - - def setUp(self): - super().setUp() - - def test_version_feedback_edit_not_logged_in(self): - url = reverse('version_feedback_edit', args=[self.plugin_1.package_name, self.version_1.version, self.feedback_1.pk]) - response = self.client.post(url, {'task': 'updated task'}) - self.assertEqual(response.status_code, 302) - self.assertIn('/accounts/login/', response.url) - - def test_version_feedback_edit_logged_in_no_permission(self): - self.user2 = User.objects.create_user(username='otheruser', password='password') - self.client.login(username='otheruser', password='password') - url = reverse('version_feedback_edit', args=[self.plugin_1.package_name, self.version_1.version, self.feedback_1.pk]) - response = self.client.post(url, {'task': 'updated task'}) - self.assertEqual(response.status_code, 401) - self.assertJSONEqual(response.content, {'success': False}) - - def test_version_feedback_edit_logged_in_with_permission(self): - self.client.force_login(user=self.creator) - url = reverse('version_feedback_edit', args=[self.plugin_1.package_name, self.version_1.version, self.feedback_1.pk]) - response = self.client.post(url, {'task': 'updated task'}) - self.assertEqual(response.status_code, 201) - self.feedback_1.refresh_from_db() - self.assertEqual(self.feedback_1.task, 'updated task') - self.assertIn('modified_on', response.json()) - - response_modified_on = response.json()['modified_on'] - expected_modified_on = self.feedback_1.modified_on.isoformat() - self.assertEqual(str(response_modified_on)[:20], expected_modified_on[:20]) - - - def test_version_feedback_edit_invalid_feedback(self): - self.client.force_login(user=self.creator) - invalid_feedback_id = self.feedback_1.pk + 1 - url = reverse('version_feedback_edit', args=[self.plugin_1.package_name, self.version_1.version, invalid_feedback_id]) - response = self.client.post(url, {'task': 'updated task'}) - self.assertEqual(response.status_code, 404) \ No newline at end of file diff --git a/qgis-app/plugins/tests/test_rename_plugin.py b/qgis-app/plugins/tests/test_rename_plugin.py deleted file mode 100644 index 78ff7d69..00000000 --- a/qgis-app/plugins/tests/test_rename_plugin.py +++ /dev/null @@ -1,111 +0,0 @@ -import os -from unittest.mock import patch - -from django.urls import reverse -from django.test import Client, TestCase, override_settings -from django.contrib.auth.models import User -from django.core.files.uploadedfile import SimpleUploadedFile -from plugins.models import Plugin, PluginVersion -from plugins.forms import PluginVersionForm - -def do_nothing(*args, **kwargs): - pass - -TESTFILE_DIR = os.path.abspath(os.path.join(os.path.dirname(__file__), "testfiles")) - -class PluginRenameTestCase(TestCase): - fixtures = [ - "fixtures/styles.json", - "fixtures/auth.json", - "fixtures/simplemenu.json", - ] - - @override_settings(MEDIA_ROOT="api/tests") - def setUp(self): - self.client = Client() - self.url_upload = reverse('plugin_upload') - - # Create a test user - self.user = User.objects.create_user( - username='testuser', - password='testpassword', - email='test@example.com' - ) - - # Log in the test user - self.client.login(username='testuser', password='testpassword') - - # Upload a plugin for renaming test. - # This process is already tested in test_plugin_upload - valid_plugin = os.path.join(TESTFILE_DIR, "valid_plugin.zip_") - with open(valid_plugin, "rb") as file: - uploaded_file = SimpleUploadedFile( - "valid_plugin.zip_", file.read(), - content_type="application/zip") - - self.client.post(self.url_upload, { - 'package': uploaded_file, - }) - - self.plugin = Plugin.objects.get(name='Test Plugin') - self.plugin.name = "New name Test Plugin" - self.plugin.save() - - @patch("plugins.tasks.generate_plugins_xml", new=do_nothing) - @patch("plugins.validator._check_url_link", new=do_nothing) - def test_plugin_rename(self): - """ - Test rename from a new plugin version - """ - package_name = self.plugin.package_name - self.url_add_version = reverse('version_create', args=[package_name]) - # Test GET request - response = self.client.get(self.url_add_version) - self.assertEqual(response.status_code, 200) - self.assertIsInstance(response.context['form'], PluginVersionForm) - - # Test POST request with invalid form data - response = self.client.post(self.url_add_version, {}) - self.assertEqual(response.status_code, 200) - self.assertFalse(response.context['form'].is_valid()) - - # Test POST request without allowing name from metadata - valid_plugin = os.path.join(TESTFILE_DIR, "valid_plugin_0.0.2.zip_") - with open(valid_plugin, "rb") as file: - uploaded_file = SimpleUploadedFile( - "valid_plugin_0.0.2.zip_", file.read(), - content_type="application/zip_") - - response = self.client.post(self.url_add_version, { - 'package': uploaded_file, - 'experimental': False, - 'changelog': '' - }) - self.assertEqual(response.status_code, 302) - self.assertTrue(PluginVersion.objects.filter( - plugin__name='New name Test Plugin', - version='0.0.2').exists() - ) - - # Test POST request with allowing name from metadata - self.plugin.allow_update_name = True - self.plugin.save() - - valid_plugin = os.path.join(TESTFILE_DIR, "valid_plugin_0.0.3.zip_") - with open(valid_plugin, "rb") as file: - uploaded_file = SimpleUploadedFile( - "valid_plugin_0.0.3.zip_", file.read(), - content_type="application/zip_") - response = self.client.post(self.url_add_version, { - 'package': uploaded_file, - 'experimental': False, - 'changelog': '' - }) - self.assertEqual(response.status_code, 302) - self.assertTrue(PluginVersion.objects.filter( - plugin__name='Test Plugin', - version='0.0.3').exists() - ) - - def tearDown(self): - self.client.logout() diff --git a/qgis-app/plugins/tests/test_simple_tag.py b/qgis-app/plugins/tests/test_simple_tag.py deleted file mode 100644 index b09d4f5b..00000000 --- a/qgis-app/plugins/tests/test_simple_tag.py +++ /dev/null @@ -1,67 +0,0 @@ -from django.contrib.auth.models import User -from django.test import TestCase -from django.urls import reverse -from plugins.models import Plugin, PluginVersion - - -class TestPluginSimpleTag(TestCase): - fixtures = ["fixtures/simplemenu.json"] - - def setUp(self) -> None: - self.creator = User.objects.create( - username="creator", email="creator@email.com" - ) - # set creator password to password - self.creator.set_password("password") - self.creator.save() - self.plugin_name = "plugin_name_test" - self.plugin = Plugin.objects.create( - created_by=self.creator, - name=self.plugin_name, - package_name=self.plugin_name, - ) - self.version = PluginVersion.objects.create( - plugin=self.plugin, - created_by=self.creator, - version="1.1.0", - min_qg_version="0.0.1", - max_qg_version="2.2.0", - ) - - def tearDown(self) -> None: - self.plugin.delete() - self.creator.delete() - self.version.delete() - - def test_return_plugin_name(self): - url = reverse( - "plugin_detail", kwargs={"package_name": self.plugin.package_name} - ) - response = self.client.get(url) - self.assertEqual(response.status_code, 200) - self.assertTrue( - bytes( - "{} — QGIS Python Plugins Repository".format(self.plugin.name), "utf-8" - ) - in response.content - ) - - def test_return_plugin_name_in_version_view(self): - url = reverse( - "version_detail", - kwargs={ - "package_name": self.plugin.package_name, - "version": self.version.version, - }, - ) - response = self.client.get(url) - self.assertEqual(response.status_code, 200) - self.assertTrue( - bytes( - "{plugin} {version} — QGIS Python Plugins Repository".format( - plugin=self.plugin.name, version=self.version.version - ), - "utf-8", - ) - in response.content - ) diff --git a/qgis-app/plugins/tests/test_task.py b/qgis-app/plugins/tests/test_task.py deleted file mode 100644 index 7cf22545..00000000 --- a/qgis-app/plugins/tests/test_task.py +++ /dev/null @@ -1,94 +0,0 @@ -import os - -from django.test import TestCase, override_settings -from django.conf import settings - -from preferences import preferences -from unittest.mock import patch, MagicMock - -from base.models.site_preferences import SitePreference -from plugins.tasks.generate_plugins_xml import generate_plugins_xml -from plugins.tasks.update_qgis_versions import update_qgis_versions - - -class TestPluginTask(TestCase): - @patch.object(SitePreference.objects, 'first') - @patch.object(SitePreference.objects, 'create') - @patch('plugins.tasks.update_qgis_versions.get_qgis_versions') - def test_update_qgis_versions(self, mock_get_qgis_versions, mock_create, mock_first): - mock_create.return_value = MagicMock() - mock_get_qgis_versions.return_value = ['3.16', '3.11', '3.12'] - site_preference = MagicMock() - site_preference.qgis_versions = '3.16,3.17' - mock_first.return_value = site_preference - - update_qgis_versions() - - self.assertEqual(site_preference.qgis_versions, '3.17,3.16,3.12,3.11') - site_preference.save.assert_called_once() - - @patch.object(SitePreference.objects, 'first') - @patch.object(SitePreference.objects, 'create') - @patch('plugins.tasks.update_qgis_versions.get_qgis_versions') - def test_update_qgis_versions_no_site_preference(self, mock_get_qgis_versions, mock_create, mock_first): - mock_get_qgis_versions.return_value = ['3.16', '3.16', '3.16'] - mock_first.return_value = None - mock_create.return_value = MagicMock() - update_qgis_versions() - mock_create.assert_called_once() - - @override_settings(DEFAULT_PLUGINS_SITE='http://test_plugins_site') - @patch('requests.get') - @patch('os.path.exists', return_value=False) - @patch('os.mkdir') - @patch('builtins.open', new_callable=MagicMock) - def test_generate_plugins_xml(self, mock_open, mock_mkdir, mock_exists, mock_get): - # Given - mock_response = MagicMock() - mock_response.status_code = 200 - mock_response.text = '' - mock_get.return_value = mock_response - preferences.SitePreference.qgis_versions = '3.24,3.25' - - expected_folder_path = os.path.join(settings.MEDIA_ROOT, 'cached_xmls') - - # When - generate_plugins_xml() - - # Then - mock_mkdir.assert_called_once_with(expected_folder_path) - expected_calls = [ - ((f'{settings.DEFAULT_PLUGINS_SITE}/plugins/plugins_new.xml?qgis=3.24',),), - ((f'{settings.DEFAULT_PLUGINS_SITE}/plugins/plugins_new.xml?qgis=3.25',),) - ] - mock_get.assert_has_calls(expected_calls, any_order=True) - mock_open.assert_any_call(os.path.join(expected_folder_path, 'plugins_3.24.xml'), 'w+') - mock_open.assert_any_call(os.path.join(expected_folder_path, 'plugins_3.25.xml'), 'w+') - - @override_settings(DEFAULT_PLUGINS_SITE='http://test_plugins_site') - @patch('requests.get') - @patch('os.path.exists', return_value=False) - @patch('os.mkdir') - @patch('builtins.open', new_callable=MagicMock) - def test_generate_plugins_xml_with_custom_site(self, mock_open, mock_mkdir, mock_exists, mock_get): - # Given - mock_response = MagicMock() - mock_response.status_code = 200 - mock_response.text = '' - mock_get.return_value = mock_response - preferences.SitePreference.qgis_versions = '3.24,3.25' - - expected_folder_path = os.path.join(settings.MEDIA_ROOT, 'cached_xmls') - - # When - generate_plugins_xml('http://custom_plugins_site') - - # Then - mock_mkdir.assert_called_once_with(expected_folder_path) - expected_calls = [ - (('http://custom_plugins_site/plugins/plugins_new.xml?qgis=3.24',),), - (('http://custom_plugins_site/plugins/plugins_new.xml?qgis=3.25',),) - ] - mock_get.assert_has_calls(expected_calls, any_order=True) - mock_open.assert_any_call(os.path.join(expected_folder_path, 'plugins_3.24.xml'), 'w+') - mock_open.assert_any_call(os.path.join(expected_folder_path, 'plugins_3.25.xml'), 'w+') diff --git a/qgis-app/plugins/tests/test_token_auth.py b/qgis-app/plugins/tests/test_token_auth.py deleted file mode 100644 index cfbca6d8..00000000 --- a/qgis-app/plugins/tests/test_token_auth.py +++ /dev/null @@ -1,163 +0,0 @@ -import os -from unittest.mock import patch - -from django.urls import reverse -from django.test import Client, TestCase, override_settings -from django.contrib.auth.models import User -from django.core.files.uploadedfile import SimpleUploadedFile -from plugins.models import Plugin, PluginVersion -from plugins.forms import PackageUploadForm -from rest_framework_simplejwt.token_blacklist.models import OutstandingToken -from rest_framework_simplejwt.tokens import RefreshToken - -def do_nothing(*args, **kwargs): - pass - -TESTFILE_DIR = os.path.abspath(os.path.join(os.path.dirname(__file__), "testfiles")) - -class UploadWithTokenTestCase(TestCase): - fixtures = [ - "fixtures/styles.json", - "fixtures/auth.json", - "fixtures/simplemenu.json", - ] - - @override_settings(MEDIA_ROOT="api/tests") - def setUp(self): - self.client = Client() - self.url_upload = reverse('plugin_upload') - - # Create a test user - self.user = User.objects.create_user( - username='testuser', - password='testpassword', - email='test@example.com' - ) - - # Log in the test user - self.client.login(username='testuser', password='testpassword') - - # Upload a plugin for renaming test. - # This process is already tested in test_plugin_upload - valid_plugin = os.path.join(TESTFILE_DIR, "valid_plugin.zip_") - with open(valid_plugin, "rb") as file: - uploaded_file = SimpleUploadedFile( - "valid_plugin.zip_", file.read(), - content_type="application/zip") - - self.client.post(self.url_upload, { - 'package': uploaded_file, - }) - - self.plugin = Plugin.objects.get(name='Test Plugin') - - package_name = self.plugin.package_name - version = '0.0.1' - self.url_add_version = reverse('version_create_api', args=[package_name]) - self.url_update_version = reverse('version_update_api', args=[package_name, version]) - self.url_token_list = reverse('plugin_token_list', args=[package_name]) - self.url_token_create = reverse('plugin_token_create', args=[package_name]) - - def test_token_create(self): - # Test token create - response = self.client.post(self.url_token_create, {}) - self.assertEqual(response.status_code, 302) - tokens = OutstandingToken.objects.all() - self.assertEqual(tokens.count(), 1) - - def test_upload_new_version_with_valid_token(self): - # Generate a token for the authenticated user - self.client.post(self.url_token_create, {}) - outstanding_token = OutstandingToken.objects.last().token - refresh = RefreshToken(outstanding_token) - refresh['plugin_id'] = self.plugin.pk - refresh['refresh_jti'] = refresh['jti'] - access_token = str(refresh.access_token) - - # Log out the user and use the token - self.client.logout() - - valid_plugin = os.path.join(TESTFILE_DIR, "valid_plugin_0.0.2.zip_") - with open(valid_plugin, "rb") as file: - uploaded_file = SimpleUploadedFile( - "valid_plugin_0.0.2.zip_", file.read(), - content_type="application/zip_") - - c = Client(HTTP_AUTHORIZATION=f"Bearer {access_token}") - - # Test POST request with access token - response = c.post(self.url_add_version, { - 'package': uploaded_file, - }) - self.assertEqual(response.status_code, 302) - self.assertTrue(PluginVersion.objects.filter(plugin__name='Test Plugin', version='0.0.2').exists()) - - def test_upload_new_version_with_invalid_token(self): - # Log out the user and use the token - self.client.logout() - - access_token = 'invalid_token' - valid_plugin = os.path.join(TESTFILE_DIR, "valid_plugin_0.0.2.zip_") - with open(valid_plugin, "rb") as file: - uploaded_file = SimpleUploadedFile( - "valid_plugin_0.0.2.zip_", file.read(), - content_type="application/zip_") - - c = Client(HTTP_AUTHORIZATION=f"Bearer {access_token}") - - # Test POST request with access token - response = c.post(self.url_add_version, { - 'package': uploaded_file, - }) - self.assertEqual(response.status_code, 403) - self.assertFalse(PluginVersion.objects.filter(plugin__name='Test Plugin', version='0.0.2').exists()) - - def test_update_version_with_valid_token(self): - # Generate a token for the authenticated user - self.client.post(self.url_token_create, {}) - outstanding_token = OutstandingToken.objects.last().token - refresh = RefreshToken(outstanding_token) - refresh['plugin_id'] = self.plugin.pk - refresh['refresh_jti'] = refresh['jti'] - access_token = str(refresh.access_token) - - # Log out the user and use the token - self.client.logout() - - valid_plugin = os.path.join(TESTFILE_DIR, "valid_plugin_0.0.2.zip_") - with open(valid_plugin, "rb") as file: - uploaded_file = SimpleUploadedFile( - "valid_plugin_0.0.2.zip_", file.read(), - content_type="application/zip_") - - c = Client(HTTP_AUTHORIZATION=f"Bearer {access_token}") - - # Test POST request with access token - response = c.post(self.url_update_version, { - 'package': uploaded_file, - }) - self.assertEqual(response.status_code, 302) - # This will create a new version because this one is using token and doesn't have a created_by column - self.assertTrue(PluginVersion.objects.filter(plugin__name='Test Plugin', version='0.0.1').exists()) - self.assertTrue(PluginVersion.objects.filter(plugin__name='Test Plugin', version='0.0.2').exists()) - - def test_update_version_with_invalid_token(self): - # Log out the user and use the token - self.client.logout() - access_token = 'invalid_token' - - valid_plugin = os.path.join(TESTFILE_DIR, "valid_plugin_0.0.2.zip_") - with open(valid_plugin, "rb") as file: - uploaded_file = SimpleUploadedFile( - "valid_plugin_0.0.2.zip_", file.read(), - content_type="application/zip_") - - c = Client(HTTP_AUTHORIZATION=f"Bearer {access_token}") - - # Test POST request with access token - response = c.post(self.url_update_version, { - 'package': uploaded_file, - }) - self.assertEqual(response.status_code, 403) - self.assertTrue(PluginVersion.objects.filter(plugin__name='Test Plugin', version='0.0.1').exists()) - self.assertFalse(PluginVersion.objects.filter(plugin__name='Test Plugin', version='0.0.2').exists()) \ No newline at end of file diff --git a/qgis-app/plugins/tests/test_utils.py b/qgis-app/plugins/tests/test_utils.py deleted file mode 100644 index c41cd8a2..00000000 --- a/qgis-app/plugins/tests/test_utils.py +++ /dev/null @@ -1,37 +0,0 @@ -from unittest.mock import patch, Mock -from django.test import TestCase -from plugins.utils import ( - get_qgis_versions, - extract_version -) - - -class TestQGISGitHubReleases(TestCase): - - @patch('requests.get') - def test_get_qgis_versions(self, mock_get): - mock_response = Mock() - mock_response.status_code = 200 - mock_response.json.return_value = [ - {'tag_name': 'final_3_22_10', 'html_url': 'https://github.com/qgis/QGIS/releases/tag/final-3_22_10'}, - {'tag_name': 'beta_3_23_0', 'html_url': 'https://github.com/qgis/QGIS/releases/tag/beta-3_23_0'} - ] - mock_get.return_value = mock_response - - versions = get_qgis_versions() - self.assertIn('3.22', versions) - - @patch('requests.get') - def test_get_github_releases_failed_request(self, mock_get): - mock_response = Mock() - mock_response.status_code = 404 - mock_get.return_value = mock_response - - with self.assertRaises(Exception) as context: - get_qgis_versions() - self.assertTrue('Request failed' in str(context.exception)) - - def test_extract_version(self): - self.assertEqual(extract_version('final-3.22.10'), '3.22') - self.assertEqual(extract_version('beta-3.23.0'), '3.23') - self.assertIsNone(extract_version('invalid-tag')) diff --git a/qgis-app/plugins/tests/test_validator.py b/qgis-app/plugins/tests/test_validator.py deleted file mode 100644 index c96ba947..00000000 --- a/qgis-app/plugins/tests/test_validator.py +++ /dev/null @@ -1,283 +0,0 @@ -import os -from unittest import mock - -import requests -from django.core.exceptions import ValidationError -from django.core.files.uploadedfile import InMemoryUploadedFile -from django.test import TestCase -from plugins.validator import _check_url_link, validator - -TESTFILE_DIR = os.path.abspath(os.path.join(os.path.dirname(__file__), "testfiles")) - - -class TestValidatorMetadataPlugins(TestCase): - def setUp(self): - invalid_plugins = os.path.join(TESTFILE_DIR, "invalid_metadata_link.zip") - invalid_url_scheme_plugins = os.path.join( - TESTFILE_DIR, "invalid_url_scheme.zip" - ) - web_not_exist_plugins = os.path.join(TESTFILE_DIR, "web_not_exist.zip") - valid_plugins = os.path.join(TESTFILE_DIR, "valid_metadata_link.zip") - self.valid_metadata_link = open(valid_plugins, "rb") - self.invalid_metadata_link = open(invalid_plugins, "rb") - self.web_not_exist = open(web_not_exist_plugins, "rb") - self.invalid_url_scheme = open(invalid_url_scheme_plugins, "rb") - - def tearDown(self): - self.valid_metadata_link.close() - self.invalid_metadata_link.close() - self.invalid_url_scheme.close() - self.web_not_exist.close() - - def test_valid_metadata(self): - self.assertTrue( - validator( - InMemoryUploadedFile( - self.valid_metadata_link, - field_name="tempfile", - name="testfile.zip", - content_type="application/zip", - size=39889, - charset="utf8", - ) - ) - ) - - def test_invalid_metadata_link_tracker_repo_homepage(self): - """ - The invalid_metadata_link.zip contains metadata file with default link - value. - - bug tracker : http://bugs - repo : http://repo - homepage : http://homepage - """ - - self.assertRaises( - ValidationError, - validator, - InMemoryUploadedFile( - self.invalid_metadata_link, - field_name="tempfile", - name="testfile.zip", - content_type="application/zip", - size=39889, - charset="utf8", - ), - ) - - def test_invalid_metadata_url_scheme(self): - """ - The invalid_url_scheme.zip contains metadata file with - invalid scheme. - - bug tracker : https ://www.example.com/invalid-url-scheme - repo : https://plugins.qgis.org/ - homepage: https://plugins.qgis.org/ - """ - - self.assertRaises( - ValidationError, - validator, - InMemoryUploadedFile( - self.invalid_url_scheme, - field_name="tempfile", - name="testfile.zip", - content_type="application/zip", - size=39889, - charset="utf8", - ), - ) - - def test_invalid_metadata_web_does_not_exist(self): - """ - The invalid_url_scheme.zip contains metadata file with - invalid scheme. - - bug tracker : http://www.example.com - repo : http://www.example.com/this-not-exist - homepage: http://www.example.com - """ - - self.assertRaises( - ValidationError, - validator, - InMemoryUploadedFile( - self.web_not_exist, - field_name="tempfile", - name="testfile.zip", - content_type="application/zip", - size=39889, - charset="utf8", - ), - ) - - @mock.patch("requests.get", side_effect=requests.exceptions.SSLError()) - def test_check_url_link_ssl_error(self, mock_request): - urls = [{'url': "http://example.com/", 'forbidden_url': "forbidden_url", 'metadata_attr': "metadata attribute"}] - self.assertIsNone(_check_url_link(urls)) - - @mock.patch("requests.get", side_effect=requests.exceptions.HTTPError()) - def test_check_url_link_does_not_exist(self, mock_request): - urls = [{'url': "http://example.com/", 'forbidden_url': "forbidden_url", 'metadata_attr': "metadata attribute"}] - self.assertIsNone(_check_url_link(urls)) - - -class TestValidatorForbiddenFileFolder(TestCase): - """Test if zipfile is not containing forbidden folders and files """ - - def setUp(self) -> None: - valid_plugins = os.path.join(TESTFILE_DIR, "valid_metadata_link.zip") - self.valid_metadata_link = open(valid_plugins, "rb") - self.package = InMemoryUploadedFile( - self.valid_metadata_link, - field_name="tempfile", - name="testfile.zip", - content_type="application/zip", - size=1234, - charset="utf8", - ) - - def tearDown(self): - self.valid_metadata_link.close() - - @mock.patch("zipfile.ZipFile.namelist") - def test_zipfile_with_pyc_file(self, mock_namelist): - mock_namelist.return_value = [".pyc"] - with self.assertRaisesMessage( - Exception, "For security reasons, zip file cannot contain .pyc file" - ): - validator(self.package) - - @mock.patch("zipfile.ZipFile.namelist") - def test_zipfile_with_MACOSX(self, mock_namelist): - mock_namelist.return_value = ["__MACOSX/"] - with self.assertRaisesMessage( - Exception, - ( - "For security reasons, zip file cannot contain '__MACOSX' directory. " - "However, there is one present at the root of the archive." - ), - ): - validator(self.package) - - @mock.patch("zipfile.ZipFile.namelist") - def test_zipfile_with_pycache(self, mock_namelist): - mock_namelist.return_value = ["__pycache__/"] - with self.assertRaisesMessage( - Exception, - ( - "For security reasons, zip file cannot contain '__pycache__' directory. " - "However, there is one present at the root of the archive." - ), - ): - validator(self.package) - - @mock.patch("zipfile.ZipFile.namelist") - def test_zipfile_with_pycache_in_children(self, mock_namelist): - mock_namelist.return_value = ["path/to/__pycache__/"] - with self.assertRaisesMessage( - Exception, - ( - "For security reasons, zip file cannot contain '__pycache__' directory. " - "However, it has been found at 'path/to/__pycache__/' ." - ), - ): - validator(self.package) - - @mock.patch("zipfile.ZipFile.namelist") - def test_zipfile_with_git(self, mock_namelist): - mock_namelist.return_value = [".git"] - with self.assertRaisesMessage( - Exception, - ( - "For security reasons, zip file cannot contain '.git' directory. " - "However, there is one present at the root of the archive." - ), - ): - validator(self.package) - - @mock.patch("zipfile.ZipFile.namelist") - def test_zipfile_with_gitignore(self, mock_namelist): - """test if .gitignore will not raise ValidationError""" - mock_namelist.return_value = [".gitignore"] - with self.assertRaises(ValidationError) as cm: - validator(self.package) - exception = cm.exception - self.assertNotEqual( - exception.message, - "For security reasons, zip file cannot contain '.git' directory. ", - "However, there is one present at the root of the archive." - ) - - -class TestLicenseValidator(TestCase): - """Test if zipfile contains LICENSE file """ - - def setUp(self) -> None: - plugin_without_license = os.path.join(TESTFILE_DIR, "plugin_without_license.zip_") - self.plugin_package = open(plugin_without_license, "rb") - - def tearDown(self): - self.plugin_package.close() - - # License file is required - def test_new_plugin_without_license(self): - self.assertRaises( - ValidationError, - validator, - InMemoryUploadedFile( - self.plugin_package, - field_name="tempfile", - name="testfile.zip", - content_type="application/zip", - size=39889, - charset="utf8", - ) - ) - -class TestMultipleParentFoldersValidator(TestCase): - """Test if zipfile contains multiple parent folders """ - - def setUp(self) -> None: - multi_parents_plugin = os.path.join(TESTFILE_DIR, "multi_parents_plugin.zip_") - self.multi_parents_plugin_package = open(multi_parents_plugin, "rb") - valid_plugin = os.path.join(TESTFILE_DIR, "valid_plugin.zip_") - self.single_parent_plugin_package = open(valid_plugin, "rb") - - def tearDown(self): - self.multi_parents_plugin_package.close() - self.single_parent_plugin_package.close() - - def _get_value_by_attribute(self, attribute, data): - for key, value in data: - if key == attribute: - return value - return None - def test_plugin_with_multiple_parents(self): - result = validator( - InMemoryUploadedFile( - self.multi_parents_plugin_package, - field_name="tempfile", - name="testfile.zip", - content_type="application/zip", - size=39889, - charset="utf8", - ) - ) - multiple_parent_folders = self._get_value_by_attribute('multiple_parent_folders', result) - self.assertIsNotNone(multiple_parent_folders) - - def test_plugin_with_single_parent(self): - result = validator( - InMemoryUploadedFile( - self.single_parent_plugin_package, - field_name="tempfile", - name="testfile.zip", - content_type="application/zip", - size=39889, - charset="utf8", - ) - ) - multiple_parent_folders = self._get_value_by_attribute('multiple_parent_folders', result) - self.assertIsNone(multiple_parent_folders) diff --git a/qgis-app/plugins/tests/test_view.py b/qgis-app/plugins/tests/test_view.py deleted file mode 100644 index 67b9fc32..00000000 --- a/qgis-app/plugins/tests/test_view.py +++ /dev/null @@ -1,27 +0,0 @@ -from django.test import TestCase - -from plugins.views import _add_patch_version - - -class TestTruncateVersion(TestCase): - """Test _add_patch_version function""" - - def test__add_patch_version_with_3_segment_version_number(self): - version = '1.2.3' - self.assertEqual(_add_patch_version(version, '99'), '1.2.3') - - def test__add_patch_version_with_2_segment_version_number(self): - version = '1.2' - self.assertEqual(_add_patch_version(version, '00'), '1.2.00') - - def test__add_patch_version_with_1_segment_version_number(self): - version = '1' - self.assertEqual(_add_patch_version(version, '99'), '1') - - def test__add_patch_version_with_None(self): - version = None - self.assertEqual(_add_patch_version(version, '99'), None) - - def test__add_patch_version_with_empty_string(self): - version = '' - self.assertEqual(_add_patch_version(version, '99'), '') diff --git a/qgis-app/plugins/tests/testfiles/change_metadata.zip_ b/qgis-app/plugins/tests/testfiles/change_metadata.zip_ deleted file mode 100644 index 4b4dd6d5..00000000 Binary files a/qgis-app/plugins/tests/testfiles/change_metadata.zip_ and /dev/null differ diff --git a/qgis-app/plugins/tests/testfiles/invalid_metadata_link.zip b/qgis-app/plugins/tests/testfiles/invalid_metadata_link.zip deleted file mode 100644 index 76f021d6..00000000 Binary files a/qgis-app/plugins/tests/testfiles/invalid_metadata_link.zip and /dev/null differ diff --git a/qgis-app/plugins/tests/testfiles/invalid_url_scheme.zip b/qgis-app/plugins/tests/testfiles/invalid_url_scheme.zip deleted file mode 100644 index e48a79d0..00000000 Binary files a/qgis-app/plugins/tests/testfiles/invalid_url_scheme.zip and /dev/null differ diff --git a/qgis-app/plugins/tests/testfiles/multi_parents_plugin.zip_ b/qgis-app/plugins/tests/testfiles/multi_parents_plugin.zip_ deleted file mode 100644 index ace51b2c..00000000 Binary files a/qgis-app/plugins/tests/testfiles/multi_parents_plugin.zip_ and /dev/null differ diff --git a/qgis-app/plugins/tests/testfiles/plugin_without_license.zip_ b/qgis-app/plugins/tests/testfiles/plugin_without_license.zip_ deleted file mode 100644 index 1f93a041..00000000 Binary files a/qgis-app/plugins/tests/testfiles/plugin_without_license.zip_ and /dev/null differ diff --git a/qgis-app/plugins/tests/testfiles/valid_metadata_link.zip b/qgis-app/plugins/tests/testfiles/valid_metadata_link.zip deleted file mode 100644 index 6c53b039..00000000 Binary files a/qgis-app/plugins/tests/testfiles/valid_metadata_link.zip and /dev/null differ diff --git a/qgis-app/plugins/tests/testfiles/valid_plugin.zip_ b/qgis-app/plugins/tests/testfiles/valid_plugin.zip_ deleted file mode 100644 index 82210e33..00000000 Binary files a/qgis-app/plugins/tests/testfiles/valid_plugin.zip_ and /dev/null differ diff --git a/qgis-app/plugins/tests/testfiles/valid_plugin_0.0.2.zip_ b/qgis-app/plugins/tests/testfiles/valid_plugin_0.0.2.zip_ deleted file mode 100644 index f5ba6212..00000000 Binary files a/qgis-app/plugins/tests/testfiles/valid_plugin_0.0.2.zip_ and /dev/null differ diff --git a/qgis-app/plugins/tests/testfiles/valid_plugin_0.0.3.zip_ b/qgis-app/plugins/tests/testfiles/valid_plugin_0.0.3.zip_ deleted file mode 100644 index 613f0f1f..00000000 Binary files a/qgis-app/plugins/tests/testfiles/valid_plugin_0.0.3.zip_ and /dev/null differ diff --git a/qgis-app/plugins/tests/testfiles/web_not_exist.zip b/qgis-app/plugins/tests/testfiles/web_not_exist.zip deleted file mode 100644 index 42bd1bdb..00000000 Binary files a/qgis-app/plugins/tests/testfiles/web_not_exist.zip and /dev/null differ diff --git a/qgis-app/plugins/tests/tests.py b/qgis-app/plugins/tests/tests.py deleted file mode 100644 index f51d798f..00000000 --- a/qgis-app/plugins/tests/tests.py +++ /dev/null @@ -1,26 +0,0 @@ -""" -This file demonstrates two different styles of tests (one doctest and one -unittest). These will both pass when you run "manage.py test". - -Replace these with more appropriate tests for your application. -""" - -from django.test import TestCase - - -class SimpleTest(TestCase): - def test_basic_addition(self): - """ - Tests that 1 + 1 always equals 2. - """ - self.assertEqual(1 + 1, 2) - - -__test__ = { - "doctest": """ -Another way to test that 1 + 1 is equal to 2. - ->>> 1 + 1 == 2 -True -""" -} diff --git a/qgis-app/plugins/tests/upload_test.py b/qgis-app/plugins/tests/upload_test.py deleted file mode 100644 index 0c05cc97..00000000 --- a/qgis-app/plugins/tests/upload_test.py +++ /dev/null @@ -1,6 +0,0 @@ -from xmlrpc import client - -server = client.ServerProxy("http://admin:admin@localhost:80/plugins/RPC2/") -handle = open("HelloWorld.zip", "rb") -blob = client.Binary(handle.read()) -server.plugin.upload(blob) diff --git a/qgis-app/plugins/tests/versionfield.py b/qgis-app/plugins/tests/versionfield.py deleted file mode 100755 index 8eff890b..00000000 --- a/qgis-app/plugins/tests/versionfield.py +++ /dev/null @@ -1,90 +0,0 @@ -#!/usr/bin/env python -# -*- coding:utf-8 -*- - -""" - Tests for version comparison field - - DESCRIPTION - - @copyright: 2014 by Alessandro Pasotti - ItOpen (http://www.itopen.it) - @license: GNU GPL, see COPYING for details. - - - -""" - -import re - -VERSION_RE = r"(^|(?<=\.))0+(?!\.)|\.#+" - -TEST_CASES = ( - "1.0.0", - "1.0.1", - "0.0.0", - "1.0", - "1.10", - "1.2", - "1.9", - "1.0.a", - "a.0.a", - "b.a.c", - "a.b", - "0.a.0.1", - "1.0.rc1", - "1.1a", - "1.1b", - "1.9.0", -) - - -def vjust(str, level=4, delim=".", bitsize=4, fillchar=" ", force_zero=False): - """ - Normalize a dotted version string. - - 1.12 becomes : 1. 12 - 1.1 becomes : 1. 1 - - - if force_zero=True and level=2: - - 1.12 becomes : 1. 12. 0 - 1.1 becomes : 1. 1. 0 - - - """ - if not str: - return str - nb = str.count(delim) - if nb < level: - if force_zero: - str += (level - nb) * (delim + "0") - else: - str += (level - nb) * delim - parts = [] - for v in str.split(delim)[: level + 1]: - if not v: - parts.append(v.rjust(bitsize, "#")) - else: - parts.append(v.rjust(bitsize, fillchar)) - return delim.join(parts) - - -def test(): - transformed = [] - for v in TEST_CASES: - vj = vjust(v, level=5, fillchar="0") - transformed.append(vj) - ck = re.sub(VERSION_RE, "", vj) - print("Testing\t %s (%s)\t\t %s" % (v, ck, vj)) - if v != ck: - print("!!! failed !!!") - - # Test sorting - transformed.sort() - print("Sorted:") - for v in transformed: - print(v) - - -if __name__ == "__main__": - test() diff --git a/qgis-app/plugins/tests/ws_test.py b/qgis-app/plugins/tests/ws_test.py deleted file mode 100644 index 2e952717..00000000 --- a/qgis-app/plugins/tests/ws_test.py +++ /dev/null @@ -1,37 +0,0 @@ -""" - -Test services - - - ->>> from django.test.client import Client ->>> c = Client() ->>> from xmlrpclib import * ->>> from django.core.management import call_command - - -Utility function: - -def xmldecode(r):p, u = getparser();p.feed(r.content);p.close();return u._stack[0] - ->>> def xmldecode(r): -... p, u = getparser() -... p.feed(r.content) -... p.close() -... return u._stack[0] - - - - ->>> c.login(username='admin', password='admin') -True - ->>> xmldecode(c.post('/plugins/RPC2', dumps(tuple(), "plugins.auth_test") , content_type = 'text/xml')) -1 - ->>> c.logout() - - - - -""" diff --git a/qgis-app/plugins/urls.py b/qgis-app/plugins/urls.py deleted file mode 100644 index e7b2d636..00000000 --- a/qgis-app/plugins/urls.py +++ /dev/null @@ -1,372 +0,0 @@ -# -*- coding: utf-8 -*- -from django.urls import re_path as url -from django.contrib.auth.decorators import login_required, user_passes_test -from django.utils.translation import gettext_lazy as _ -from plugins.models import Plugin, PluginVersion -from plugins.views import * -from rpc4django.views import serve_rpc_request - -# Plugins filtered views (need user parameter from request) -urlpatterns = [ - # XML - url(r"^plugins_new.xml$", xml_plugins_new, {}, name="xml_plugins_new"), - url(r"^plugins.xml$", xml_plugins, {}, name="xml_plugins"), - url( - r"^plugins_(?P\d+\.\d+).xml$", - xml_plugins, - {}, - name="xml_plugins_version_filtered_cached", - ), - url( - r"^version_filtered/(?P\d+\.\d+).xml$", - xml_plugins, - {}, - name="xml_plugins_version_filtered_uncached", - ), - url(r"^tags/(?P[^\/]+)/$", TagsPluginsList.as_view(), name="tags_plugins"), - url(r"^add/$", plugin_upload, {}, name="plugin_upload"), - url(r"^user/(?P\w+)/block/$", user_block, {}, name="user_block"), - url(r"^user/(?P\w+)/unblock/$", user_unblock, {}, name="user_unblock"), - url(r"^user/(?P\w+)/trust/$", user_trust, {}, name="user_trust"), - url(r"^user/(?P\w+)/untrust/$", user_untrust, {}, name="user_untrust"), - url( - r"^(?P[A-Za-z][A-Za-z0-9-_]+)/manage/$", - plugin_manage, - {}, - name="plugin_manage", - ), - url( - r"^(?P[A-Za-z][A-Za-z0-9-_]+)/delete/$", - plugin_delete, - {}, - name="plugin_delete", - ), - url( - r"^(?P[A-Za-z][A-Za-z0-9-_]+)/update/$", - plugin_update, - {}, - name="plugin_update", - ), - url( - r"^(?P[A-Za-z][A-Za-z0-9-_]+)/tokens/$", - PluginTokenListView.as_view(), - name="plugin_token_list", - ), - url( - r"^(?P[A-Za-z][A-Za-z0-9-_]+)/tokens/(?P\d+)/$", - PluginTokenDetailView.as_view(), - name="plugin_token_detail", - ), - url( - r"^(?P[A-Za-z][A-Za-z0-9-_]+)/tokens/create/$", - plugin_token_create, - {}, - name="plugin_token_create", - ), - url( - r"^(?P[A-Za-z][A-Za-z0-9-_]+)/tokens/(?P\d+)/update$", - plugin_token_update, - {}, - name="plugin_token_update", - ), - url( - r"^(?P[A-Za-z][A-Za-z0-9-_]+)/tokens/(?P[^\/]+)/delete/$", - plugin_token_delete, - {}, - name="plugin_token_delete", - ), - url( - r"^(?P[A-Za-z][A-Za-z0-9-_]+)/set_featured/$", - plugin_set_featured, - {}, - name="plugin_set_featured", - ), - url( - r"^(?P[A-Za-z][A-Za-z0-9-_]+)/unset_featured/$", - plugin_unset_featured, - {}, - name="plugin_unset_featured", - ), - url( - r"^user/(?P\w+)/admin$", - UserDetailsPluginsList.as_view(), - name="user_details", - ), - url(r"^$", PluginsList.as_view(), name="approved_plugins"), - url( - r"^my$", - login_required( - MyPluginsList.as_view(additional_context={"title": _("My Plugins")}) - ), - name="my_plugins", - ), - url( - r"^featured/$", - PluginsList.as_view( - queryset=Plugin.featured_objects.all(), - additional_context={"title": _("Featured plugins")}, - ), - name="featured_plugins", - ), - url(r"^user/(?P\w+)/$", UserPluginsList.as_view(), name="user_plugins"), - url( - r"^server/$", - PluginsList.as_view( - queryset=Plugin.server_objects.all(), - additional_context={"title": _("QGIS Server plugins")}, - ), - name="server_plugins", - ), - url( - r"^unapproved/$", - PluginsList.as_view( - queryset=Plugin.unapproved_objects.all().order_by("-latest_version_date"), - additional_context={"title": _("Unapproved plugins")}, - ), - name="unapproved_plugins", - ), - url( - r"^deprecated/$", - PluginsList.as_view( - queryset=Plugin.deprecated_objects.all(), - additional_context={"title": _("Deprecated plugins")}, - ), - name="deprecated_plugins", - ), - url( - r"^fresh/$", - PluginsList.as_view( - queryset=Plugin.fresh_objects.all(), - additional_context={"title": _("New plugins")}, - ), - name="fresh_plugins", - ), - url( - r"^latest/$", - PluginsList.as_view( - queryset=Plugin.latest_objects.all(), - additional_context={"title": _("Updated plugins")}, - ), - name="latest_plugins", - ), - url( - r"^stable/$", - PluginsList.as_view( - queryset=Plugin.stable_objects.all(), - additional_context={"title": _("Stable plugins")}, - ), - name="stable_plugins", - ), - url( - r"^experimental/$", - PluginsList.as_view( - queryset=Plugin.experimental_objects.all(), - additional_context={"title": _("Experimental plugins")}, - ), - name="experimental_plugins", - ), - url( - r"^popular/$", - PluginsList.as_view( - queryset=Plugin.popular_objects.all(), - additional_context={"title": _("Popular plugins")}, - ), - name="popular_plugins", - ), - url( - r"^most_voted/$", - PluginsList.as_view( - queryset=Plugin.most_voted_objects.all(), - additional_context={"title": _("Most voted plugins")}, - ), - name="most_voted_plugins", - ), - url( - r"^most_downloaded/$", - PluginsList.as_view( - queryset=Plugin.most_downloaded_objects.all(), - additional_context={"title": _("Most downloaded plugins")}, - ), - name="most_downloaded_plugins", - ), - url( - r"^most_voted/$", - PluginsList.as_view( - queryset=Plugin.most_voted_objects.all(), - additional_context={"title": _("Most voted plugins")}, - ), - name="most_voted_plugins", - ), - url( - r"^most_rated/$", - PluginsList.as_view( - queryset=Plugin.most_rated_objects.all(), - additional_context={"title": _("Most rated plugins")}, - ), - name="most_rated_plugins", - ), - url( - r"^feedback_completed/$", - FeedbackCompletedPluginsList.as_view( - additional_context={"title": _("Reviewed Plugins (Resolved)")} - ), - name="feedback_completed_plugins", - ), - url( - r"^feedback_pending/$", - FeedbackPendingPluginsList.as_view( - additional_context={"title": _("Awaiting review")} - ), - name="feedback_pending_plugins", - ), - url( - r"^feedback_received/$", - FeedbackReceivedPluginsList.as_view( - additional_context={"title": _("Reviewed Plugins (Pending)")} - ), - name="feedback_received_plugins", - ), - url( - r"^author/(?P[^/]+)/$", - AuthorPluginsList.as_view(), - name="author_plugins", - ), -] - - -# User management -urlpatterns += [ - url( - r"^user/(?P\w+)/manage/$", - user_permissions_manage, - {}, - name="user_permissions_manage", - ), -] - - -# Version Management -urlpatterns += [ - url( - r"^(?P[A-Za-z][A-Za-z0-9-_]+)/version/(?P[^\/]+)/manage/$", - version_manage, - {}, - name="version_manage", - ), - url( - r"^(?P[A-Za-z][A-Za-z0-9-_]+)/version/add/$", - version_create, - {}, - name="version_create", - ), - url( - r"^api/(?P[A-Za-z][A-Za-z0-9-_]+)/version/add/$", - version_create_api, - {}, - name="version_create_api", - ), - url( - r"^(?P[A-Za-z][A-Za-z0-9-_]+)/version/(?P[^\/]+)/$", - version_detail, - {}, - name="version_detail", - ), - url( - r"^(?P[A-Za-z][A-Za-z0-9-_]+)/version/(?P[^\/]+)/delete/$", - version_delete, - {}, - name="version_delete", - ), - url( - r"^(?P[A-Za-z][A-Za-z0-9-_]+)/version/(?P[^\/]+)/update/$", - version_update, - {}, - name="version_update", - ), - url( - r"^api/(?P[A-Za-z][A-Za-z0-9-_]+)/version/(?P[^\/]+)/update/$", - version_update_api, - {}, - name="version_update_api", - ), - url( - r"^(?P[A-Za-z][A-Za-z0-9-_]+)/version/(?P[^\/]+)/download/$", - version_download, - {}, - name="version_download", - ), - url( - r"^(?P[A-Za-z][A-Za-z0-9-_]+)/version/(?P[^\/]+)/approve/$", - version_approve, - {}, - name="version_approve", - ), - url( - r"^(?P[A-Za-z][A-Za-z0-9-_]+)/version/(?P[^\/]+)/unapprove/$", - version_unapprove, - {}, - name="version_unapprove", - ), - url( - r"^(?P[A-Za-z][A-Za-z0-9-_]+)/version/(?P[^\/]+)/feedback/$", - version_feedback, - {}, - name="version_feedback", - ), - url( - r"^(?P[A-Za-z][A-Za-z0-9-_]+)/version/(?P[^\/]+)/feedback/update/$", - version_feedback_update, - {}, - name="version_feedback_update", - ), - url( - r"^(?P[A-Za-z][A-Za-z0-9-_]+)/version/(?P[^\/]+)/feedback/(?P[0-9]+)/delete/$", - version_feedback_delete, - {}, - name="version_feedback_delete", - ), - url( - r"^(?P[A-Za-z][A-Za-z0-9-_]+)/version/(?P[^\/]+)/feedback/(?P[0-9]+)/edit/$", - version_feedback_edit, - {}, - name="version_feedback_edit", - ), -] - -# RPC -urlpatterns += [ - # rpc4django will need to be in your Python path - url(r"^RPC2/$", serve_rpc_request), -] - - -from django.views.decorators.csrf import csrf_protect, ensure_csrf_cookie -from django.views.decorators.http import require_POST - -# plugin rating -from djangoratings.views import AddRatingFromModel - -urlpatterns += [ - url( - r"rate/(?P\d+)/(?P\d+)/", - require_POST(csrf_protect(AddRatingFromModel())), - { - "app_label": "plugins", - "model": "plugin", - "field_name": "rating", - }, - name="plugin_rate", - ), -] - - -# Plugin detail (keep last) -urlpatterns += [ - url( - r"^(?P[A-Za-z][A-Za-z0-9-_]+)/$", - PluginDetailView.as_view( - slug_url_kwarg="package_name", slug_field="package_name" - ), - name="plugin_detail", - ), -] diff --git a/qgis-app/plugins/utils.py b/qgis-app/plugins/utils.py deleted file mode 100644 index 521a5797..00000000 --- a/qgis-app/plugins/utils.py +++ /dev/null @@ -1,58 +0,0 @@ -import requests -import re -from django.http import HttpRequest - - -def extract_version(tag): - """ - Extracts the major and minor version from a given tag. - - The tag should be in the format of x.y.z where x, y, and z are - numbers representing major, minor, and patch versions respectively. - - Args: - tag (str): The version tag to be processed. - - Returns: - str: The major and minor version as x.y, or None if no match. - """ - match = re.search(r'(\d+\.\d+\.\d+)', tag) - if match: - version = match.group(1) - version_parts = version.split('.') - return '.'.join(version_parts[:-1]) - else: - return None - - -def get_qgis_versions(): - """ - Fetches all releases from the QGIS GitHub repository and extracts their - major and minor versions. - - Returns: - list: A list of unique major and minor versions of the releases. - - Raises: - Exception: If the request to the GitHub API fails. - """ - url = 'https://api.github.com/repos/qgis/QGIS/releases' - response = requests.get(url) - if response.status_code != 200: - raise Exception('Request failed') - releases = response.json() - all_versions = [] - for release in releases: - tag_name = release['tag_name'].replace('_', '.') - version = extract_version(tag_name) - if version not in all_versions: - all_versions.append(version) - return all_versions - - -def parse_remote_addr(request: HttpRequest) -> str: - """Extract client IP from request.""" - x_forwarded_for = request.headers.get("X-Forwarded-For", "") - if x_forwarded_for: - return x_forwarded_for.split(",")[0] - return request.META.get("REMOTE_ADDR", "") \ No newline at end of file diff --git a/qgis-app/plugins/validator.py b/qgis-app/plugins/validator.py deleted file mode 100644 index a6a9f2b2..00000000 --- a/qgis-app/plugins/validator.py +++ /dev/null @@ -1,387 +0,0 @@ -""" -Plugin validator class - -""" -import codecs -import configparser -import mimetypes -import os -import re -import zipfile -from io import StringIO -from urllib.parse import urlparse - -import requests -from django.conf import settings -from django.core.files.uploadedfile import SimpleUploadedFile -from django.forms import ValidationError -from django.utils.translation import gettext_lazy as _ - -PLUGIN_MAX_UPLOAD_SIZE = getattr(settings, "PLUGIN_MAX_UPLOAD_SIZE", 25000000) # 25 mb -PLUGIN_REQUIRED_METADATA = getattr( - settings, - "PLUGIN_REQUIRED_METADATA", - ( - "name", - "description", - "version", - "qgisMinimumVersion", - "author", - "email", - "about", - "tracker", - "repository", - ), -) - -PLUGIN_OPTIONAL_METADATA = getattr( - settings, - "PLUGIN_OPTIONAL_METADATA", - ( - "homepage", - "changelog", - "qgisMaximumVersion", - "tags", - "deprecated", - "experimental", - "external_deps", - "server", - ), -) -PLUGIN_BOOLEAN_METADATA = getattr( - settings, "PLUGIN_BOOLEAN_METADATA", ("experimental", "deprecated", "server") -) - - -def _read_from_init(initcontent, initname): - """ - Read metadata from __init__.py, raise ValidationError - """ - metadata = [] - i = 0 - lines = initcontent.split("\n") - while i < len(lines): - if re.search(r"def\s+([^\(]+)", lines[i]): - k = re.search(r"def\s+([^\(]+)", lines[i]).groups()[0] - i += 1 - while i < len(lines) and lines[i] != "": - if re.search(r"return\s+[\"']?([^\"']+)[\"']?", lines[i]): - metadata.append( - ( - k, - re.search( - r"return\s+[\"']?([^\"']+)[\"']?", lines[i] - ).groups()[0], - ) - ) - break - i += 1 - i += 1 - if not len(metadata): - raise ValidationError(_("Cannot find valid metadata in %s") % initname) - return metadata - - -def _check_required_metadata(metadata): - """ - Checks if required metadata are in place, raise ValidationError if not found - """ - - missing_fields = [field for field in PLUGIN_REQUIRED_METADATA if field not in [item[0] for item in metadata]] - if len(missing_fields) > 0: - missing_fields_str = ', '.join(missing_fields) - raise ValidationError( - _( - f'Cannot find metadata {missing_fields_str} in metadata source {dict(metadata).get("metadata_source")}.
For further informations about metadata, please see: metadata documentation' - ) - ) - -def _check_url_link(urls): - """ - Checks if all the url link is valid. - """ - def error_check(url: str, forbidden_url: str)->bool: - # Check against forbidden_url - if url == forbidden_url: - return True - - # Check if parsed URL is valid - try: - parsed_url = urlparse(url) - return not all([parsed_url.scheme, parsed_url.netloc]) - except Exception as e: - # Log the exception or handle it as per your requirement - print(f"Error occurred: {e}") - return True - - def error_check_if_exist(url: str)->bool: - # Check if url is exist - try: - # https://stackoverflow.com/a/41950438/10268058 - # add the headers parameter to make the request appears like coming - # from browser, otherwise some websites will return 403 - headers = { - "User-Agent": "Mozilla/5.0 (Windows NT 6.1; WOW64) " - "AppleWebKit/537.36 (KHTML, like Gecko) " - "Chrome/56.0.2924.76 Safari/537.36" - } - req = requests.head(url, headers=headers) - except requests.exceptions.SSLError: - req = requests.head(url, verify=False) - except Exception: - return True - return req.status_code >= 400 - - url_error = [item for item in [url_item['metadata_attr'] for url_item in urls if error_check(url_item['url'], url_item['forbidden_url'])]] - if len(url_error) > 0: - url_error_str = ", ".join(url_error) - raise ValidationError( - _(f"Please provide valid url link for the following key(s) in the metadata source: {url_error_str}. ") - ) - exist_url_error = [item for item in [url_item['metadata_attr'] for url_item in urls if error_check_if_exist(url_item['url'])]] - if len(exist_url_error) > 0: - exist_url_error_str = ", ".join(exist_url_error) - raise ValidationError( - _( - f"Please provide valid url link for the following key(s) in the metadata source: {exist_url_error_str}. " - "The website(s) cannot be reached." - ) - ) - - -def validator(package): - """ - Analyzes a zipped file, returns metadata if success, False otherwise. - If the new icon metadata is found, an inmemory file object is also returned - - Current checks: - - * size <= PLUGIN_MAX_UPLOAD_SIZE - * zip contains __init__.py in first level dir - * Check for LICENSE file - * mandatory metadata: ('name', 'description', 'version', 'qgisMinimumVersion', 'author', 'email') - * package_name regexp: [A-Za-z][A-Za-z0-9-_]+ - * author regexp: [^/]+ - - """ - try: - if package.size > PLUGIN_MAX_UPLOAD_SIZE: - raise ValidationError( - _("File is too big. Max size is %s Megabytes") - % (PLUGIN_MAX_UPLOAD_SIZE / 1000000) - ) - except AttributeError: - if package.len > PLUGIN_MAX_UPLOAD_SIZE: - raise ValidationError( - _("File is too big. Max size is %s Megabytes") - % (PLUGIN_MAX_UPLOAD_SIZE / 1000000) - ) - - try: - zip = zipfile.ZipFile(package) - except: - raise ValidationError(_("Could not unzip file.")) - for zname in zip.namelist(): - if zname.find("..") != -1 or zname.find(os.path.sep) == 0: - raise ValidationError( - _("For security reasons, zip file cannot contain path " - "information (found '{}')".format(zname)) - ) - if zname.find(".pyc") != -1: - raise ValidationError( - _("For security reasons, zip file cannot contain .pyc file") - ) - for forbidden_dir in ["__MACOSX", ".git", "__pycache__"]: - dir_name_list = zname.split("/") - if forbidden_dir in dir_name_list: - if forbidden_dir == dir_name_list[0]: - raise ValidationError( - _( - "For security reasons, zip file " - "cannot contain '%s' directory. However, there is one present at the root of the archive." % (forbidden_dir,) - ) - ) - raise ValidationError( - _( - "For security reasons, zip file " - "cannot contain '%s' directory. However, it has been found at '%s' ." % (forbidden_dir, zname) - ) - ) - bad_file = zip.testzip() - if bad_file: - zip.close() - del zip - try: - raise ValidationError( - _("Bad zip (maybe a CRC error) on file %s") % bad_file - ) - except UnicodeDecodeError: - raise ValidationError( - _("Bad zip (maybe unicode filename) on file %s") % bad_file, - errors="replace", - ) - - # Metadata list, also usefull to pass warnings to the main view - metadata = [] - - namelist = zip.namelist() - # Check if the zip file contains multiple parent folders - # If it is, show a warning for now - try: - parent_folders = list(set([str(name).split('/')[0] for name in namelist])) - if len(parent_folders) > 1: - metadata.append(("multiple_parent_folders", ', '.join(parent_folders))) - except: - pass - - # Checks that package_name exists - try: - package_name = namelist[0][: namelist[0].index("/")] - except: - raise ValidationError( - _( - "Cannot find a folder inside the compressed package: this does not seems a valid plugin" - ) - ) - - # Cuts the trailing slash - if package_name.endswith("/"): - package_name = package_name[:-1] - initname = package_name + "/__init__.py" - metadataname = package_name + "/metadata.txt" - if initname not in namelist and metadataname not in namelist: - raise ValidationError( - _( - "Cannot find __init__.py or metadata.txt in the compressed package: this does not seems a valid plugin (I searched for %s and %s)" - ) - % (initname, metadataname) - ) - - # Checks for __init__.py presence - if initname not in namelist: - raise ValidationError(_("Cannot find __init__.py in plugin package.")) - - # First parse metadata.txt - if metadataname in namelist: - try: - parser = configparser.ConfigParser() - parser.optionxform = str - parser.read_file(StringIO(codecs.decode(zip.read(metadataname), "utf8"))) - if not parser.has_section("general"): - raise ValidationError( - _("Cannot find a section named 'general' in %s") % metadataname - ) - metadata.extend(parser.items("general")) - except Exception as e: - raise ValidationError(_("Errors parsing %s. %s") % (metadataname, e)) - metadata.append(("metadata_source", "metadata.txt")) - else: - # Then parse __init__ - # Ugly RE: regexp guru wanted! - initcontent = zip.read(initname).decode("utf8") - metadata.extend(_read_from_init(initcontent, initname)) - if not metadata: - raise ValidationError(_("Cannot find valid metadata in %s") % initname) - metadata.append(("metadata_source", "__init__.py")) - - _check_required_metadata(metadata) - - # Process Icon - try: - # Strip leading dir for ccrook plugins - if dict(metadata)["icon"].startswith("./"): - icon_path = dict(metadata)["icon"][2:] - else: - icon_path = dict(metadata)["icon"] - icon = zip.read(package_name + "/" + icon_path) - icon_file = SimpleUploadedFile( - dict(metadata)["icon"], icon, mimetypes.guess_type(dict(metadata)["icon"]) - ) - except: - icon_file = None - - metadata.append(("icon_file", icon_file)) - - # Transforms booleans flags (experimental) - for flag in PLUGIN_BOOLEAN_METADATA: - if flag in dict(metadata): - metadata[metadata.index((flag, dict(metadata)[flag]))] = ( - flag, - dict(metadata)[flag].lower() == "true" - or dict(metadata)[flag].lower() == "1", - ) - - # Adds package_name - if not re.match(r"^[A-Za-z][A-Za-z0-9-_]+$", package_name): - raise ValidationError( - _( - "The name of the top level directory inside the zip package must start with an ASCII letter and can only contain ASCII letters, digits and the signs '-' and '_'." - ) - ) - metadata.append(("package_name", package_name)) - - # Last temporary rule, check if mandatory metadata are also in __init__.py - # fails if it is not - min_qgs_version = dict(metadata).get("qgisMinimumVersion") - dict(metadata).get("qgisMaximumVersion") - if ( - tuple(min_qgs_version.split(".")) < tuple("1.8".split(".")) - and metadataname in namelist - ): - initcontent = zip.read(initname).decode("utf8") - try: - initmetadata = _read_from_init(initcontent, initname) - initmetadata.append(("metadata_source", "__init__.py")) - _check_required_metadata(initmetadata) - except ValidationError as e: - raise ValidationError( - _( - "qgisMinimumVersion is set to less than 1.8 (%s) and there were errors reading metadata from the __init__.py file. This can lead to errors in versions of QGIS less than 1.8, please either set the qgisMinimumVersion to 1.8 or specify the metadata also in the __init__.py file. Reported error was: %s" - ) - % (min_qgs_version, ",".join(e.messages)) - ) - # check url_link - urls_to_check = [ - {'url': dict(metadata).get("tracker"), 'forbidden_url': "http://bugs", 'metadata_attr': "tracker"}, - {'url': dict(metadata).get("repository"), 'forbidden_url': "http://repo", 'metadata_attr': "repository"}, - {'url': dict(metadata).get("homepage"), 'forbidden_url': "http://homepage", 'metadata_attr': "homepage"}, - ] - - _check_url_link(urls_to_check) - - - # Checks for LICENSE file presence - # Making it mandatory as of 03 June 2024 - # according to https://github.com/qgis/QGIS-Enhancement-Proposals/issues/279 - licensename = package_name + "/LICENSE" - if licensename not in namelist: - raise ValidationError(_( - "Cannot find LICENSE in the plugin package. " - "This file is required, please consider adding it to the plugin package.") - ) - - zip.close() - del zip - - # Check author - if "author" in dict(metadata): - if not re.match(r"^[^/]+$", dict(metadata)["author"]): - raise ValidationError(_("Author name cannot contain slashes.")) - - # strip and check - checked_metadata = [] - for k, v in metadata: - try: - if not (k in PLUGIN_BOOLEAN_METADATA or k == "icon_file"): - # v.decode('UTF-8') - checked_metadata.append((k, v.strip())) - else: - checked_metadata.append((k, v)) - except UnicodeDecodeError as e: - raise ValidationError( - _( - "There was an error converting metadata '%s' to UTF-8 . Reported error was: %s" - ) - % (k, e) - ) - return checked_metadata diff --git a/qgis-app/plugins/views.py b/qgis-app/plugins/views.py deleted file mode 100644 index 57ef6a8e..00000000 --- a/qgis-app/plugins/views.py +++ /dev/null @@ -1,1864 +0,0 @@ -# Create your views here. -import copy -import logging -import os -import datetime - -from django.conf import settings -from django.contrib import messages -from django.contrib.auth.decorators import login_required, user_passes_test -from django.contrib.auth.models import Permission, User -from django.contrib.contenttypes.models import ContentType -from django.contrib.sites.models import Site -from django.core.exceptions import FieldDoesNotExist -from django.core.mail import send_mail -from django.db import IntegrityError, connection -from django.db.models import Max, Q -from django.db.models.expressions import RawSQL -from django.db.models.functions import Lower -from django.http import Http404, HttpResponse, HttpResponseRedirect, JsonResponse -from django.shortcuts import get_object_or_404, render -from django.urls import reverse -from django.utils.timezone import now -from django.utils.decorators import method_decorator -from django.utils.encoding import DjangoUnicodeDecodeError -from django.utils.translation import gettext_lazy as _ -from django.views.decorators.cache import never_cache -from django.views.decorators.csrf import ensure_csrf_cookie, csrf_exempt, csrf_protect -from django.views.decorators.http import require_POST -from django.views.generic.detail import DetailView -from django.db import transaction - -# from sortable_listview import SortableListView -from django.views.generic.list import ListView -from plugins.decorators import has_valid_token -from plugins.forms import * -from plugins.models import Plugin, PluginOutstandingToken, PluginVersion, PluginVersionDownload, vjust -from plugins.validator import PLUGIN_REQUIRED_METADATA -from django.contrib.gis.geoip2 import GeoIP2 -from plugins.utils import parse_remote_addr - -from rest_framework_simplejwt.token_blacklist.models import OutstandingToken -from rest_framework_simplejwt.tokens import RefreshToken, api_settings -from rest_framework_simplejwt.exceptions import InvalidToken, TokenError -import time -try: - from urllib import unquote, urlencode - - from urlparse import parse_qs, urlparse -except ImportError: - from urllib.parse import parse_qs, unquote, urlencode, urlparse - -# Decorator -staff_required = user_passes_test(lambda u: u.is_staff) -from plugins.tasks.generate_plugins_xml import generate_plugins_xml - - -def send_mail_wrapper(subject, message, mail_from, recipients, fail_silently=True): - if settings.DEBUG: - logging.debug("Mail not sent (DEBUG=True)") - else: - send_mail(subject, message, mail_from, recipients, fail_silently) - - -def plugin_notify(plugin): - """ - Sends a message to staff on new plugins - """ - recipients = [ - u.email - for u in User.objects.filter(is_staff=True, email__isnull=False).exclude( - email="" - ) - ] - - if recipients: - domain = Site.objects.get_current().domain - mail_from = settings.DEFAULT_FROM_EMAIL - - send_mail_wrapper( - _("A new plugin has been created by %s.") % plugin.created_by, - _( - "\r\nPlugin name is: %s\r\nPlugin description is: %s\r\nLink: http://%s%s\r\n" - ) - % (plugin.name, plugin.description, domain, plugin.get_absolute_url()), - mail_from, - recipients, - fail_silently=True, - ) - logging.debug( - "Sending email notification for %s plugin, recipients: %s" - % (plugin, recipients) - ) - else: - logging.warning("No recipients found for %s plugin notification" % plugin) - - -def version_notify(plugin_version): - """ - Sends a message to staff on new plugin versions - """ - plugin = plugin_version.plugin - - recipients = [ - u.email - for u in User.objects.filter(is_staff=True, email__isnull=False).exclude( - email="" - ) - ] - - if recipients: - domain = Site.objects.get_current().domain - mail_from = settings.DEFAULT_FROM_EMAIL - - send_mail_wrapper( - _("A new plugin version has been uploaded by %s.") % plugin.created_by, - _( - "\r\nPlugin name is: %s\r\nPlugin description is: %s\r\nLink: http://%s%s\r\n" - ) - % ( - plugin.name, - plugin.description, - domain, - plugin_version.get_absolute_url(), - ), - mail_from, - recipients, - fail_silently=True, - ) - logging.debug( - "Sending email notification for %s plugin version, recipients: %s" - % (plugin_version, recipients) - ) - else: - logging.warning( - "No recipients found for %s plugin version notification" % plugin_version - ) - - -def plugin_approve_notify(plugin, msg, user): - """ - Sends a message when a plugin is approved or unapproved. - """ - if settings.DEBUG: - return - recipients = [u.email for u in plugin.editors if u.email] - if settings.QGIS_DEV_MAILING_LIST_ADDRESS: - recipients.append(settings.QGIS_DEV_MAILING_LIST_ADDRESS) - if plugin.approved: - approval_state = "approval" - else: - approval_state = "unapproval" - - if len(recipients): - domain = Site.objects.get_current().domain - mail_from = settings.DEFAULT_FROM_EMAIL - logging.debug( - "Sending email %s notification for %s plugin, recipients: %s" - % (approval_state, plugin, recipients) - ) - send_mail_wrapper( - _("Plugin %s %s notification.") % (plugin, approval_state), - _("\r\nPlugin %s %s by %s.\r\n%s\r\nLink: http://%s%s\r\n") - % ( - plugin.name, - approval_state, - user, - msg, - domain, - plugin.get_absolute_url(), - ), - mail_from, - recipients, - fail_silently=True, - ) - else: - logging.warning( - "No recipients found for %s plugin %s notification" - % (plugin, approval_state) - ) - - -def version_feedback_notify(version, user): - """ - Sends a message when a version is receiving feedback. - """ - if settings.DEBUG: - return - plugin = version.plugin - recipients = [u.email for u in plugin.editors if u.email] - if recipients: - domain = Site.objects.get_current().domain - mail_from = settings.DEFAULT_FROM_EMAIL - logging.debug( - "Sending email feedback notification for %s plugin version %s, recipients: %s" - % (plugin, version.version, recipients) - ) - send_mail_wrapper( - _("Plugin %s feedback notification.") % (plugin, ), - _("\r\nPlugin %s reviewed by %s and received a feedback.\r\nLink: http://%s%sfeedback/\r\n") - % ( - plugin.name, - user, - domain, - version.get_absolute_url(), - ), - mail_from, - recipients, - fail_silently=True, - ) - else: - logging.warning( - "No recipients found for %s plugin feedback notification" - % (plugin, ) - ) - - -def user_trust_notify(user): - """ - Sends a message when an author is trusted or untrusted. - """ - if settings.DEBUG: - return - if user.is_staff: - logging.debug("Skipping trust notification for staff user %s" % user) - else: - if user.email: - recipients = [user.email] - mail_from = settings.DEFAULT_FROM_EMAIL - - if user.has_perm("plugins.can_approve"): - subject = _("User trust notification.") - message = _( - "\r\nYou can now approve your own plugins and the plugins you can edit.\r\n" - ) - else: - subject = _("User untrust notification.") - message = _("\r\nYou cannot approve any plugin.\r\n") - - logging.debug("Sending email trust change notification to %s" % recipients) - send_mail_wrapper( - subject, message, mail_from, recipients, fail_silently=True - ) - else: - logging.warning( - "No email found for %s user trust change notification" % user - ) - - -## Access control ## - - -def check_plugin_access(user, plugin): - """ - Returns true if the user can modify the plugin: - - * is_staff - * is owner - - """ - return user.is_staff or user in plugin.editors - -def check_plugin_token_access(user, plugin): - """ - Returns true if the user can access all the plugin's token: - - * is_staff - * is maintainer - - """ - return user.is_staff or user.pk == plugin.created_by.pk - - -def check_plugin_version_approval_rights(user, plugin): - """ - Returns true if the user can approve the plugin version: - - * is_staff - * is owner and is trusted - - """ - return user.is_staff or ( - user in plugin.editors and user.has_perm("plugins.can_approve") - ) - - -@login_required -def plugin_create(request): - """ - The form will automatically set published flag according to user permissions. - There is a more "automatic" alternative for creating new Plugins in a single step - through package upload - """ - if request.method == "POST": - form = PluginForm(request.POST, request.FILES) - form.fields["owners"].queryset = User.objects.exclude( - pk=request.user.pk - ).order_by("username") - if form.is_valid(): - plugin = form.save(commit=False) - plugin.created_by = request.user - plugin.save() - plugin_notify(plugin) - msg = _("The Plugin has been successfully created.") - messages.success(request, msg, fail_silently=True) - return HttpResponseRedirect(plugin.get_absolute_url()) - else: - form = PluginForm() - form.fields["owners"].queryset = User.objects.exclude( - pk=request.user.pk - ).order_by("username") - - return render( - request, - "plugins/plugin_form.html", - {"form": form, "form_title": _("New plugin")}, - ) - - -@staff_required -@require_POST -def plugin_set_featured(request, package_name): - """ - Set as featured - """ - plugin = get_object_or_404(Plugin, package_name=package_name) - plugin.featured = True - plugin.save() - msg = _("The plugin %s is now a marked as featured." % plugin) - messages.success(request, msg, fail_silently=True) - return HttpResponseRedirect(plugin.get_absolute_url()) - - -@staff_required -@require_POST -def plugin_unset_featured(request, package_name): - """ - Sets as not featured - """ - plugin = get_object_or_404(Plugin, package_name=package_name) - plugin.featured = False - plugin.save() - msg = _("The plugin %s is not marked as featured anymore." % plugin) - messages.success(request, msg, fail_silently=True) - return HttpResponseRedirect(plugin.get_absolute_url()) - - -@login_required -def plugin_upload(request): - """ - This is the "single step" way to create new plugins: - uploads a package and creates a new Plugin with a new PluginVersion - can also update an existing plugin - """ - if request.method == "POST": - form = PackageUploadForm(request.POST, request.FILES) - if form.is_valid(): - try: - plugin_data = { - "name": form.cleaned_data["name"], - "package_name": form.cleaned_data["package_name"], - "description": form.cleaned_data["description"], - "created_by": request.user, - "author": form.cleaned_data["author"], - "email": form.cleaned_data["email"], - "created_by": request.user, - "icon": form.cleaned_data["icon_file"], - } - - # Gets existing plugin - try: - plugin = Plugin.objects.get( - package_name=plugin_data["package_name"] - ) - if not check_plugin_access(request.user, plugin): - return render( - request, "plugins/plugin_permission_deny.html", {} - ) - # Apply new values - plugin.name = plugin_data["name"] - plugin.description = plugin_data["description"] - plugin.author = plugin_data["author"] - plugin.email = plugin_data["email"] - is_new = False - except Plugin.DoesNotExist: - plugin = Plugin(**plugin_data) - is_new = True - - # Check icon, don't change if not valid - if plugin_data["icon"]: - plugin.icon = plugin_data["icon"] - - # Server is optional - plugin.server = form.cleaned_data.get("server", False) - - # Other optional fields - warnings = [] - - if form.cleaned_data.get("homepage"): - plugin.homepage = form.cleaned_data.get("homepage") - elif not plugin.homepage: - warnings.append( - _( - "homepage field is empty, this field is not required but is recommended, please consider adding it to metadata." - ) - ) - if form.cleaned_data.get("tracker"): - plugin.tracker = form.cleaned_data.get("tracker") - elif not plugin.tracker: - raise ValidationError( - _( - '"tracker" metadata is required! Please add it to metadata.txt.' - ) - ) - if form.cleaned_data.get("repository"): - plugin.repository = form.cleaned_data.get("repository") - elif not plugin.repository: - raise ValidationError( - _( - '"repository" metadata is required! Please add it to metadata.txt.' - ) - ) - if form.cleaned_data.get("about"): - plugin.about = form.cleaned_data.get("about") - elif not plugin.about: - raise ValidationError( - _( - '"about" metadata is required! Please add it to metadata.txt.' - ) - ) - - # Save main Plugin object - plugin.save() - - if is_new: - plugin_notify(plugin) - - # Takes care of tags - if form.cleaned_data.get("tags"): - plugin.tags.set( - [ - t.strip().lower() - for t in form.cleaned_data.get("tags").split(",") - ] - ) - - version_data = { - "plugin": plugin, - "min_qg_version": form.cleaned_data.get("qgisMinimumVersion"), - "max_qg_version": form.cleaned_data.get("qgisMaximumVersion"), - "version": form.cleaned_data.get("version"), - "created_by": request.user, - "package": form.cleaned_data.get("package"), - "approved": request.user.has_perm("plugins.can_approve") - or plugin.approved, - "experimental": form.cleaned_data.get("experimental"), - "changelog": form.cleaned_data.get("changelog", ""), - "external_deps": form.cleaned_data.get("external_deps", ""), - } - - new_version = PluginVersion(**version_data) - new_version.save() - msg = _("The Plugin has been successfully created.") - messages.success(request, msg, fail_silently=True) - - # Update plugins cached xml - generate_plugins_xml.delay() - - if not new_version.approved: - msg = _( - "Your plugin is awaiting approval from a staff member and will be approved as soon as possible." - ) - warnings.append(msg) - if not is_new: - version_notify(new_version) - if not form.cleaned_data.get("metadata_source") == "metadata.txt": - msg = _( - "Your plugin does not contain a metadata.txt file, metadata have been read from the __init__.py file. This is deprecated and its support will eventually cease." - ) - warnings.append(msg) - - # Grouped messages: - if warnings: - messages.warning( - request, - _("

Warnings:

") - + "\n".join([("

%s

" % w) for w in warnings]), - fail_silently=True, - ) - - if form.cleaned_data.get("multiple_parent_folders"): - parent_folders = form.cleaned_data.get("multiple_parent_folders") - messages.warning( - request, - _( - f"Your plugin includes multiple parent folders: {parent_folders}. Please be aware that only the first folder has been recognized. It is strongly advised to have a single parent folder." - ), - fail_silently=True, - ) - del form.cleaned_data["multiple_parent_folders"] - - except (IntegrityError, ValidationError, DjangoUnicodeDecodeError) as e: - connection.close() - messages.error(request, e, fail_silently=True) - if not plugin.pk: - return render(request, "plugins/plugin_upload.html", {"form": form}) - return HttpResponseRedirect(plugin.get_absolute_url()) - else: - form = PackageUploadForm() - - return render(request, "plugins/plugin_upload.html", {"form": form}) - - -class PluginDetailView(DetailView): - model = Plugin - queryset = Plugin.objects.all() - - @method_decorator(ensure_csrf_cookie) - def dispatch(self, *args, **kwargs): - return super(PluginDetailView, self).dispatch(*args, **kwargs) - - def get_context_data(self, **kwargs): - plugin = kwargs.get("object") - context = super(PluginDetailView, self).get_context_data(**kwargs) - # Warnings for owners - if check_plugin_access(self.request.user, plugin): - if not plugin.homepage: - msg = _( - 'homepage metadata is missing, this is not required but recommended. Please consider adding "homepage" to metadata.txt.' - ) - messages.warning(self.request, msg, fail_silently=True) - for md in set(PLUGIN_REQUIRED_METADATA) - set( - ("version", "qgisMinimumVersion") - ): - if not getattr(plugin, md, None): - msg = _( - "%s metadata is missing, this metadata entry is required. Please add %s to metadata.txt." - ) % (md, md) - messages.error(self.request, msg, fail_silently=True) - stats_url = f"{settings.METABASE_DOWNLOAD_STATS_URL}?package_name={plugin.package_name}#hide_parameters=package_name" - context.update( - { - "stats_url": stats_url, - "rating": plugin.rating.get_rating(), - "votes": plugin.rating.votes, - } - ) - return context - - -@login_required -def plugin_delete(request, package_name): - plugin = get_object_or_404(Plugin, package_name=package_name) - if not check_plugin_access(request.user, plugin): - return render(request, "plugins/plugin_permission_deny.html", {}) - if "delete_confirm" in request.POST: - plugin.delete() - msg = _("The Plugin has been successfully deleted.") - messages.success(request, msg, fail_silently=True) - return HttpResponseRedirect(reverse("approved_plugins")) - return render(request, "plugins/plugin_delete_confirm.html", {"plugin": plugin}) - - -def _check_optional_metadata(form, request): - """ - Checks for the presence of optional metadata - """ - if not form.cleaned_data.get("homepage"): - messages.warning( - request, - _( - "Homepage field is empty, this field is not required but is recommended, please consider adding it to metadata.txt." - ), - fail_silently=True, - ) - - -@login_required -def plugin_update(request, package_name): - """ - Plugin update form - """ - plugin = get_object_or_404(Plugin, package_name=package_name) - if not check_plugin_access(request.user, plugin): - return render(request, "plugins/plugin_permission_deny.html", {}) - if request.method == "POST": - form = PluginForm(request.POST, request.FILES, instance=plugin) - form.fields["owners"].queryset = User.objects.exclude( - pk=plugin.created_by.pk - ).order_by("username") - if form.is_valid(): - new_object = form.save(commit=False) - new_object.modified_by = request.user - new_object.save() - # Without this next line the tags won't be saved. - form.save_m2m() - new_object.owners.clear() - for o in form.cleaned_data["owners"]: - new_object.owners.add(o) - msg = _("The Plugin has been successfully updated.") - messages.success(request, msg, fail_silently=True) - - # Checks for optional metadata - _check_optional_metadata(form, request) - - return HttpResponseRedirect(new_object.get_absolute_url()) - else: - form = PluginForm(instance=plugin) - form.fields["owners"].queryset = User.objects.exclude( - pk=plugin.created_by.pk - ).order_by("username") - - return render( - request, - "plugins/plugin_form.html", - {"form": form, "form_title": _("Edit plugin"), "plugin": plugin}, - ) - - - -class PluginTokenListView(ListView): - """ - Plugin token list - """ - model = PluginOutstandingToken - queryset = PluginOutstandingToken.objects.all().order_by("-token__created_at") - template_name = "plugins/plugin_token_list.html" - - @method_decorator(ensure_csrf_cookie) - def dispatch(self, *args, **kwargs): - return super(PluginTokenListView, self).dispatch(*args, **kwargs) - - def get_filtered_queryset(self, qs): - package_name = self.kwargs.get('package_name') - plugin = get_object_or_404(Plugin, package_name=package_name) - if not check_plugin_token_access(self.request.user, plugin): - return qs.filter( - plugin__pk=plugin.pk, - is_blacklisted=False, - token__user=self.request.user - ) - return qs.filter( - plugin__pk=plugin.pk, - is_blacklisted=False, - ) - - def get_queryset(self): - qs = super(PluginTokenListView, self).get_queryset() - qs = self.get_filtered_queryset(qs) - return qs - - def get_context_data(self, **kwargs): - package_name = self.kwargs.get('package_name') - plugin = get_object_or_404(Plugin, package_name=package_name) - if not check_plugin_access(self.request.user, plugin): - context = {} - self.template_name = "plugins/plugin_token_permission_deny.html" - return context - context = super(PluginTokenListView, self).get_context_data(**kwargs) - context.update( - { - "plugin": plugin - } - ) - return context - -class PluginTokenDetailView(DetailView): - """ - Plugin token detail - """ - model = OutstandingToken - queryset = OutstandingToken.objects.all() - template_name = "plugins/plugin_token_detail.html" - - @method_decorator(ensure_csrf_cookie) - def dispatch(self, *args, **kwargs): - return super(PluginTokenDetailView, self).dispatch(*args, **kwargs) - - def get_context_data(self, **kwargs): - context = super(PluginTokenDetailView, self).get_context_data(**kwargs) - package_name = self.kwargs.get('package_name') - token_id = self.kwargs.get('pk') - plugin = get_object_or_404(Plugin, package_name=package_name) - if not check_plugin_access(self.request.user, plugin): - context = {} - self.template_name = "plugins/plugin_token_permission_deny.html" - return context - - outstanding_token = get_object_or_404(OutstandingToken, pk=token_id, user=self.request.user) - plugin_token = get_object_or_404( - PluginOutstandingToken, - token__pk=outstanding_token.pk, - is_blacklisted=False, - is_newly_created=True - ) - try: - token = RefreshToken(outstanding_token.token) - token['plugin_id'] = plugin.pk - token['refresh_jti'] = token[api_settings.JTI_CLAIM] - del token['user_id'] - except (InvalidToken, TokenError) as e: - context = {} - self.template_name = "plugins/plugin_token_invalid_or_expired.html" - return context - timestamp_from_last_edit = int(time.time()) - context.update( - { - "access_token": str(token.access_token), - "plugin": plugin, - "object": outstanding_token, - 'timestamp_from_last_edit': timestamp_from_last_edit - } - ) - plugin_token.is_newly_created = False - plugin_token.save() - return context - -@login_required -@transaction.atomic -def plugin_token_create(request, package_name): - if request.method == "POST": - plugin = get_object_or_404(Plugin, package_name=package_name) - user = request.user - if not check_plugin_access(user, plugin): - return render(request, "plugins/plugin_permission_deny.html", {}) - - refresh = RefreshToken.for_user(user) - refresh["plugin_id"] = plugin.pk - - jti = refresh[api_settings.JTI_CLAIM] - - outstanding_token = OutstandingToken.objects.get(jti=jti) - - plugin_token = PluginOutstandingToken.objects.create( - plugin=plugin, - token=outstanding_token, - is_blacklisted=False, - is_newly_created=True - ) - - return HttpResponseRedirect( - reverse("plugin_token_detail", args=(plugin.package_name, plugin_token.pk)) - ) - -@login_required -@transaction.atomic -def plugin_token_update(request, package_name, token_id): - plugin = get_object_or_404(Plugin, package_name=package_name) - outstanding_token = get_object_or_404(OutstandingToken, pk=token_id) - if not check_plugin_token_access(request.user, plugin): - outstanding_token = get_object_or_404(OutstandingToken, pk=token_id, user=request.user) - plugin_token = get_object_or_404( - PluginOutstandingToken, - token__pk=outstanding_token.pk, - is_blacklisted=False - ) - if not check_plugin_access(request.user, plugin): - return render(request, "plugins/version_permission_deny.html", {}) - if request.method == "POST": - form = PluginTokenForm(request.POST, instance=plugin_token) - if form.is_valid(): - form.save() - msg = _("The token description has been successfully updated.") - messages.success(request, msg, fail_silently=True) - return HttpResponseRedirect( - reverse("plugin_token_list", args=(plugin.package_name,)) - ) - else: - form = PluginTokenForm(instance=plugin_token) - - return render( - request, - "plugins/plugin_token_form.html", - {"form": form, "token": plugin_token} - ) - -@login_required -@transaction.atomic -def plugin_token_delete(request, package_name, token_id): - plugin = get_object_or_404(Plugin, package_name=package_name) - outstanding_token = get_object_or_404(OutstandingToken, pk=token_id) - if not check_plugin_token_access(request.user, plugin): - outstanding_token = get_object_or_404(OutstandingToken, pk=token_id, user=request.user) - plugin_token = get_object_or_404( - PluginOutstandingToken, - token__pk=outstanding_token.pk, - is_blacklisted=False - ) - - if not check_plugin_access(request.user, plugin): - return render(request, "plugins/version_permission_deny.html", {}) - if "delete_confirm" in request.POST: - try: - token = RefreshToken(outstanding_token.token) - token.blacklist() - plugin_token.is_blacklisted = True - except (InvalidToken, TokenError) as e: - plugin_token.is_blacklisted = True - plugin_token.save() - - msg = _("The token has been successfully deleted.") - messages.success(request, msg, fail_silently=True) - return HttpResponseRedirect( - reverse("plugin_token_list", args=(plugin.package_name,)) - ) - return render( - request, - "plugins/plugin_token_delete_confirm.html", - {"plugin": plugin, "username": outstanding_token.user}, - ) - - -class PluginsList(ListView): - model = Plugin - queryset = Plugin.approved_objects.all() - title = _("All plugins") - additional_context = {} - paginate_by = settings.PAGINATION_DEFAULT_PAGINATION - - def get_paginate_by(self, queryset): - """ - Paginate by specified value in querystring, or use default class property value. - """ - try: - paginate_by = int(self.request.GET.get("per_page", self.paginate_by)) - except ValueError: - paginate_by = self.paginate_by - return paginate_by - - def get_filtered_queryset(self, qs): - return qs - - def get_queryset(self): - qs = super(PluginsList, self).get_queryset() - qs = self.get_filtered_queryset(qs) - sort_by = self.request.GET.get("sort", None) - if sort_by: - if sort_by[0] == "-": - _sort_by = sort_by[1:] - else: - _sort_by = sort_by - - # Check if the sort criterion is a field or 'average_vote' - # or 'latest_version_date' - try: - ( - _sort_by == "average_vote" - or _sort_by == "latest_version_date" - or self.model._meta.get_field(_sort_by) - ) - except FieldDoesNotExist: - return qs - qs = qs.order_by(sort_by) - else: - # default - if not qs.ordered: - qs = qs.order_by(Lower("name")) - return qs - - def get_context_data(self, **kwargs): - context = super(PluginsList, self).get_context_data(**kwargs) - context.update( - { - "title": self.title, - } - ) - context.update(self.additional_context) - context["current_sort_query"] = self.get_sortstring() - context["current_querystring"] = self.get_querystring() - context["per_page_list"] = [20, 50, 75, 100] - - try: - # Get the next value of per page from per_page_list - next_per_page_id = context["per_page_list"].index(context["paginator"].per_page) + 1 - next_per_page = context["per_page_list"][next_per_page_id] - except (ValueError, IndexError): - # If the 'per_page' value in the request parameter - # is not found in the 'per_page_list' or if the - # next index is out of range, set the 'next_per_page' - # value to a number greater than the total count - # of records. This action effectively disables the button." - next_per_page = context["paginator"].count + 1 - context["show_more_items_number"] = next_per_page - return context - - def get_sortstring(self): - if self.request.GET.get("sort", None): - return "sort=%s" % self.request.GET.get("sort") - return "" - - def get_querystring(self): - """ - Clean existing query string (GET parameters) by removing - arguments that we don't want to preserve (sort parameter, 'page') - """ - to_remove = ["page", "sort"] - query_string = urlparse(self.request.get_full_path()).query - query_dict = parse_qs(query_string) - for arg in to_remove: - if arg in query_dict: - del query_dict[arg] - clean_query_string = urlencode(query_dict, doseq=True) - return clean_query_string - - -class MyPluginsList(PluginsList): - def get_filtered_queryset(self, qs): - return ( - Plugin.base_objects.filter(owners=self.request.user).distinct() - | Plugin.objects.filter(created_by=self.request.user).distinct() - ) - - -class UserPluginsList(PluginsList): - def get_filtered_queryset(self, qs): - user = get_object_or_404(User, username=self.kwargs["username"]) - return qs.filter(created_by=user) - - -class AuthorPluginsList(PluginsList): - def get_filtered_queryset(self, qs): - return qs.filter(author=unquote(self.kwargs["author"])) - - def get_context_data(self, **kwargs): - context = super(AuthorPluginsList, self).get_context_data(**kwargs) - context.update( - { - "title": _("Plugins by %s") % unquote(self.kwargs["author"]), - } - ) - return context - - -class UserDetailsPluginsList(PluginsList): - """ - List plugins created_by OR owned by user - """ - - template_name = "plugins/user.html" - - def get_filtered_queryset(self, qs): - user = get_object_or_404(User, username=self.kwargs["username"]) - return qs.filter(Q(created_by=user) | Q(owners=user)) - - def get_context_data(self, **kwargs): - user = get_object_or_404(User, username=self.kwargs["username"]) - user_is_trusted = user.has_perm("plugins.can_approve") - context = super(UserDetailsPluginsList, self).get_context_data(**kwargs) - context.update( - { - "title": _("Plugins from %s") % user, - "user_is_trusted": user_is_trusted, - "plugin_user": user, - } - ) - return context - - -class TagsPluginsList(PluginsList): - def get_filtered_queryset(self, qs): - response = qs.filter(tagged_items__tag__slug=unquote(self.kwargs["tags"])) - return response - - def get_context_data(self, **kwargs): - context = super(TagsPluginsList, self).get_context_data(**kwargs) - context.update( - { - "title": _("Plugins tagged with: %s") % unquote(self.kwargs["tags"]), - "page_title": _("Tag: %s") % unquote(self.kwargs["tags"]) - } - ) - return context - - -class FeedbackCompletedPluginsList(PluginsList): - """List of Plugins that has feedback resolved in its versions. - - The plugins editor can only see their plugin feedbacks. - The staff can see all plugin feedbacks. - """ - queryset = Plugin.feedback_completed_objects.all().order_by("-latest_version_date") - - def get_filtered_queryset(self, qs): - user = get_object_or_404(User, username=self.request.user) - if not user.is_staff: - raise Http404 - return qs - -class FeedbackReceivedPluginsList(PluginsList): - """List of Plugins that has feedback received in its versions. - - The plugins editor can only see their plugin feedbacks. - The staff can see all plugin feedbacks. - """ - queryset = Plugin.feedback_received_objects.all().order_by("-latest_version_date") - - def get_filtered_queryset(self, qs): - user = get_object_or_404(User, username=self.request.user) - if not user.is_staff: - raise Http404 - return qs - -class FeedbackPendingPluginsList(PluginsList): - """List of Plugins that has feedback pending in its versions. - - Only staff can see plugin feedback list. - """ - queryset = Plugin.feedback_pending_objects.all().order_by("-latest_version_date") - - def get_filtered_queryset(self, qs): - user = get_object_or_404(User, username=self.request.user) - if not user.is_staff: - raise Http404 - return qs - - -@login_required -@require_POST -def plugin_manage(request, package_name): - """ - Entry point for the plugin management functions - """ - if request.POST.get("set_featured"): - return plugin_set_featured(request, package_name) - if request.POST.get("unset_featured"): - return plugin_unset_featured(request, package_name) - if request.POST.get("delete"): - return plugin_delete(request, package_name) - - return HttpResponseRedirect(reverse("user_details", args=[username])) - - -############################################### - -# User management functions - -############################################### - - -@staff_required -@require_POST -def user_block(request, username): - """ - Completely blocks a user - """ - user = get_object_or_404(User, username=username, is_staff=False) - # Disable - user.is_active = False - user.save() - msg = _("The user %s is now blocked." % user) - messages.success(request, msg, fail_silently=True) - return HttpResponseRedirect(reverse("user_details", args=[user.username])) - - -@staff_required -@require_POST -def user_unblock(request, username): - """ - unblocks a user - """ - user = get_object_or_404(User, username=username, is_staff=False) - # Enable - user.is_active = True - user.save() - msg = _("The user %s is now unblocked." % user) - messages.success(request, msg, fail_silently=True) - return HttpResponseRedirect(reverse("user_details", args=[user.username])) - - -@staff_required -@require_POST -def user_trust(request, username): - """ - Assigns can_approve permission to the plugin creator - """ - user = get_object_or_404(User, username=username) - user.user_permissions.add( - Permission.objects.get( - codename="can_approve", - content_type=ContentType.objects.get(app_label="plugins", model="plugin"), - ) - ) - msg = _("The user %s is now a trusted user." % user) - messages.success(request, msg, fail_silently=True) - user_trust_notify(user) - return HttpResponseRedirect(reverse("user_details", args=[user.username])) - - -@staff_required -@require_POST -def user_untrust(request, username): - """ - Revokes can_approve permission to the plugin creator - """ - user = get_object_or_404(User, username=username) - user.user_permissions.remove( - Permission.objects.get( - codename="can_approve", - content_type=ContentType.objects.get(app_label="plugins", model="plugin"), - ) - ) - msg = _("The user %s is now an untrusted user." % user) - messages.success(request, msg, fail_silently=True) - user_trust_notify(user) - return HttpResponseRedirect(reverse("user_details", args=[user.username])) - - -@staff_required -@require_POST -def user_permissions_manage(request, username): - """ - Entry point for the user management functions - """ - if request.POST.get("user_block"): - return user_block(request, username) - if request.POST.get("user_unblock"): - return user_unblock(request, username) - if request.POST.get("user_trust"): - return user_trust(request, username) - if request.POST.get("user_untrust"): - return user_untrust(request, username) - - return HttpResponseRedirect(reverse("user_details", args=[username])) - - -############################################### - -# Version management functions - -############################################### - - -def _main_plugin_update(request, plugin, form): - """ - Updates the main plugin object from version metadata - """ - # Check if update name from metadata is allowed - metadata_fields = ["author", "email", "description", "about", "homepage", "tracker", "repository"] - if plugin.allow_update_name: - metadata_fields.insert(0, "name") - - # Update plugin from metadata - for f in metadata_fields: - if form.cleaned_data.get(f): - setattr(plugin, f, form.cleaned_data.get(f)) - - # Icon has a special treatment - if form.cleaned_data.get("icon_file"): - setattr(plugin, "icon", form.cleaned_data.get("icon_file")) - if form.cleaned_data.get("tags"): - plugin.tags.set( - [t.strip().lower() for t in form.cleaned_data.get("tags").split(",")] - ) - plugin.save() - -@has_valid_token -@csrf_exempt -def version_create_api(request, package_name): - """ - Create a new version using a valid token. - We make sure that the token is valid before - disabling CSRF protection. - """ - plugin = get_object_or_404(Plugin, package_name=package_name) - version = PluginVersion(plugin=plugin, is_from_token=True, token=request.plugin_token) - - return _version_create(request, plugin, version) - - -@login_required -def version_create(request, package_name): - plugin = get_object_or_404(Plugin, package_name=package_name) - if not check_plugin_access(request.user, plugin): - return render( - request, "plugins/version_permission_deny.html", {"plugin": plugin} - ) - version = PluginVersion(plugin=plugin, created_by=request.user) - is_trusted=request.user.has_perm("plugins.can_approve") - return _version_create(request, plugin, version, is_trusted=is_trusted) - -def _version_create(request, plugin, version, is_trusted=False): - """ - The form will create versions according to permissions, - plugin name and description are updated according to the info - contained in the package metadata - """ - if request.method == "POST": - - form = PluginVersionForm( - request.POST, - request.FILES, - instance=version, - is_trusted=is_trusted - ) - if form.is_valid(): - try: - new_object = form.save() - msg = _("The Plugin Version has been successfully created.") - messages.success(request, msg, fail_silently=True) - # The approved flag is also controlled in the form, but we - # are checking it here in any case for additional security - if not is_trusted: - new_object.approved = False - new_object.save() - messages.warning( - request, - _( - "You do not have approval permissions, plugin version has been set unapproved." - ), - fail_silently=True, - ) - version_notify(new_object) - if form.cleaned_data.get("icon_file"): - form.cleaned_data["icon"] = form.cleaned_data.get("icon_file") - - if form.cleaned_data.get("multiple_parent_folders"): - parent_folders = form.cleaned_data.get("multiple_parent_folders") - messages.warning( - request, - _( - f"Your plugin includes multiple parent folders: {parent_folders}. Please be aware that only the first folder has been recognized. It is strongly advised to have a single parent folder." - ), - fail_silently=True, - ) - del form.cleaned_data["multiple_parent_folders"] - - _main_plugin_update(request, new_object.plugin, form) - _check_optional_metadata(form, request) - return HttpResponseRedirect(new_object.plugin.get_absolute_url()) - except (IntegrityError, ValidationError, DjangoUnicodeDecodeError) as e: - messages.error(request, e, fail_silently=True) - connection.close() - return HttpResponseRedirect(plugin.get_absolute_url()) - else: - form = PluginVersionForm( - is_trusted=is_trusted - ) - - return render( - request, - "plugins/version_form.html", - {"form": form, "plugin": plugin, "form_title": _("New version for plugin")}, - ) - - -@has_valid_token -@csrf_exempt -def version_update_api(request, package_name, version): - """ - Update a version using a valid token. - We make sure that the token is valid before - disabling CSRF protection. - """ - plugin = get_object_or_404(Plugin, package_name=package_name) - version = PluginVersion(plugin=plugin, is_from_token=True, token=request.plugin_token) - return _version_update(request, plugin, version) - - -@login_required -def version_update(request, package_name, version): - plugin = get_object_or_404(Plugin, package_name=package_name) - version = get_object_or_404(PluginVersion, plugin=plugin, version=version) - if not check_plugin_access(request.user, plugin): - return render( - request, "plugins/version_permission_deny.html", {"plugin": plugin} - ) - version = PluginVersion(plugin=plugin, created_by=request.user) - is_trusted=request.user.has_perm("plugins.can_approve") - return _version_update(request, plugin, version, is_trusted=is_trusted) - -def _version_update(request, plugin, version, is_trusted=False): - """ - The form will update versions according to permissions - """ - - if request.method == "POST": - form = PluginVersionForm( - request.POST, - request.FILES, - instance=version, - is_trusted=is_trusted, - ) - if form.is_valid(): - try: - new_object = form.save() - # update metadata for the main plugin object - _main_plugin_update(request, new_object.plugin, form) - msg = _("The Plugin Version has been successfully updated.") - messages.success(request, msg, fail_silently=True) - - if form.cleaned_data.get("multiple_parent_folders"): - parent_folders = form.cleaned_data.get("multiple_parent_folders") - messages.warning( - request, - _( - f"Your plugin includes multiple parent folders: {parent_folders}. Please be aware that only the first folder has been recognized. It is strongly advised to have a single parent folder." - ), - fail_silently=True, - ) - del form.cleaned_data["multiple_parent_folders"] - - except (IntegrityError, ValidationError, DjangoUnicodeDecodeError) as e: - messages.error(request, e, fail_silently=True) - connection.close() - return HttpResponseRedirect(plugin.get_absolute_url()) - else: - form = PluginVersionForm( - instance=version, is_trusted=is_trusted - ) - - return render( - request, - "plugins/version_form.html", - { - "form": form, - "plugin": plugin, - "version": version, - "form_title": _("Edit version for plugin"), - }, - ) - - -@login_required -def version_delete(request, package_name, version): - plugin = get_object_or_404(Plugin, package_name=package_name) - version = get_object_or_404(PluginVersion, plugin=plugin, version=version) - if not check_plugin_access(request.user, plugin): - return render(request, "plugins/version_permission_deny.html", {}) - if "delete_confirm" in request.POST: - version.delete() - msg = _("The Plugin Version has been successfully deleted.") - messages.success(request, msg, fail_silently=True) - return HttpResponseRedirect( - reverse("plugin_detail", args=(plugin.package_name,)) - ) - return render( - request, - "plugins/version_delete_confirm.html", - {"plugin": plugin, "version": version}, - ) - - -@login_required -@require_POST -def version_approve(request, package_name, version): - """ - Approves the plugin version - """ - plugin = get_object_or_404(Plugin, package_name=package_name) - version = get_object_or_404(PluginVersion, plugin=plugin, version=version) - if not check_plugin_version_approval_rights(request.user, version.plugin): - msg = _("You do not have approval rights for this plugin.") - messages.error(request, msg, fail_silently=True) - return HttpResponseRedirect(version.get_absolute_url()) - version.approved = True - version.save() - msg = _( - "The plugin version '%s' is now approved. " - "Please note that there may be a delay of up to 15 minutes " - "between the approval of the plugin and its actual availability in the XML." - ) % version - messages.success(request, msg, fail_silently=True) - plugin_approve_notify(version.plugin, msg, request.user) - try: - redirect_to = request.META["HTTP_REFERER"] - except: - redirect_to = version.get_absolute_url() - return HttpResponseRedirect(redirect_to) - - -@login_required -@require_POST -def version_unapprove(request, package_name, version): - """ - unapproves the plugin version - """ - plugin = get_object_or_404(Plugin, package_name=package_name) - version = get_object_or_404(PluginVersion, plugin=plugin, version=version) - if not check_plugin_version_approval_rights(request.user, version.plugin): - msg = _("You do not have approval rights for this plugin.") - messages.error(request, msg, fail_silently=True) - return HttpResponseRedirect(version.get_absolute_url()) - version.approved = False - version.save() - msg = _('The plugin version "%s" is now unapproved' % version) - messages.success(request, msg, fail_silently=True) - plugin_approve_notify(version.plugin, msg, request.user) - try: - redirect_to = request.META["HTTP_REFERER"] - except: - redirect_to = version.get_absolute_url() - return HttpResponseRedirect(redirect_to) - - -@login_required -@require_POST -def version_manage(request, package_name, version): - """ - Entry point for the user management functions - """ - if "version_approve" in request.POST: - return version_approve(request, package_name, version) - if "version_unapprove" in request.POST: - return version_unapprove(request, package_name, version) - - return HttpResponseRedirect(reverse("plugin_detail", args=[package_name])) - - -@login_required -@never_cache -def version_feedback(request, package_name, version): - """ - The form will add a comment/ feedback for the package version. - """ - plugin = get_object_or_404(Plugin, package_name=package_name) - version = get_object_or_404(PluginVersion, plugin=plugin, version=version) - is_user_plugin_owner: bool = request.user in plugin.editors - is_user_has_approval_rights: bool = check_plugin_version_approval_rights( - request.user, plugin) - if not is_user_plugin_owner and not is_user_has_approval_rights: - return render( - request, - template_name="plugins/version_permission_deny.html", - context={}, - status=403 - ) - if request.method == "POST": - form = VersionFeedbackForm(request.POST) - if form.is_valid(): - tasks = form.cleaned_data['tasks'] - for task in tasks: - PluginVersionFeedback.objects.create( - version=version, - reviewer=request.user, - task=task - ) - version_feedback_notify(version, request.user) - form = VersionFeedbackForm() - feedbacks = PluginVersionFeedback.objects.filter(version=version) - return render( - request, - "plugins/plugin_feedback.html", - { - "feedbacks": feedbacks, - "form": form, - "version": version, - "is_user_has_approval_rights": is_user_has_approval_rights, - "is_user_plugin_owner": is_user_plugin_owner - } - ) - - -@login_required -@require_POST -def version_feedback_update(request, package_name, version): - plugin = get_object_or_404(Plugin, package_name=package_name) - version = get_object_or_404(PluginVersion, plugin=plugin, version=version) - has_update_permission: bool = ( - request.user in plugin.editors - or check_plugin_version_approval_rights(request.user, plugin) - ) - if not has_update_permission: - return JsonResponse({"success": False}, status=401) - completed_tasks = request.POST.getlist('completed_tasks') - for task_id in completed_tasks: - try: - task_id = int(task_id) - except ValueError: - continue - feedback = PluginVersionFeedback.objects.filter( - version=version, pk=task_id).first() - feedback.is_completed = True - feedback.save() - return JsonResponse({"success": True}, status=201) - - -@login_required -@require_POST -def version_feedback_edit(request, package_name, version, feedback): - feedback = get_object_or_404( - PluginVersionFeedback, - version__plugin__package_name=package_name, - version__version=version, - pk=feedback - ) - plugin = feedback.version.plugin - - has_update_permission: bool = ( - request.user in plugin.editors - or check_plugin_version_approval_rights(request.user, plugin) - ) - if not has_update_permission: - return JsonResponse({"success": False}, status=401) - task = request.POST.get('task') - feedback.task = str(task) - feedback.modified_on = datetime.datetime.now() - feedback.save() - return JsonResponse({"success": True, "modified_on": feedback.modified_on}, status=201) - -@login_required -@require_POST -def version_feedback_delete(request, package_name, version, feedback): - feedback = get_object_or_404( - PluginVersionFeedback, - version__plugin__package_name=package_name, - version__version=version, - pk=feedback - ) - plugin = feedback.version.plugin - status = request.POST.get('status_feedback') - is_update_succeed: bool = False - is_user_can_update_feedback: bool = ( - request.user in plugin.editors - or check_plugin_version_approval_rights(request.user, plugin) - ) - if status == "deleted" and feedback.reviewer == request.user: - feedback.delete() - is_update_succeed: bool = True - elif (status == "completed" or status == "uncompleted") and ( - is_user_can_update_feedback): - feedback.is_completed = (status == "completed") - feedback.save() - is_update_succeed: bool = True - return JsonResponse({"success": is_update_succeed}) - - -def version_download(request, package_name, version): - """ - Update download counter(s) - """ - plugin = get_object_or_404(Plugin, package_name=package_name) - version = get_object_or_404(PluginVersion, plugin=plugin, version=version) - version.downloads = version.downloads + 1 - version.save() - plugin = version.plugin - plugin.downloads = plugin.downloads + 1 - plugin.save(keep_date=True) - - remote_addr = parse_remote_addr(request) - g = GeoIP2() - - if remote_addr: - try: - country_data = g.country(remote_addr) - country_code = country_data['country_code'] - country_name = country_data['country_name'] - except Exception as e: # AddressNotFoundErrors: - country_code = 'N/D' - country_name = 'N/D' - - # Handle null values - country_code = country_code or 'N/D' - country_name = country_name or 'N/D' - - download_record, created = PluginVersionDownload.objects.get_or_create( - plugin_version = version, - country_code = country_code, - country_name = country_name, - download_date = now().date(), - defaults = {'download_count': 1} - ) - if not created: - download_record.download_count = ( - download_record.download_count + 1 - ) - download_record.save() - - if not version.package.file.file.closed: - version.package.file.file.close() - zipfile = open(version.package.file.name, "rb") - file_content = zipfile.read() - response = HttpResponse(file_content, content_type="application/zip") - response["Content-Disposition"] = "attachment; filename=%s-%s.zip" % ( - version.plugin.package_name, - version.version, - ) - return response - - -def version_detail(request, package_name, version): - """ - Show version details - """ - plugin = get_object_or_404(Plugin, package_name=package_name) - version = get_object_or_404(PluginVersion, plugin=plugin, version=version) - return render(request, "plugins/version_detail.html", {"version": version}) - - -############################################### - -# Misc functions - -############################################### - -from django.views.decorators.cache import cache_page - - -def _add_patch_version(version: str, additional_patch: str ) -> str: - """To add patch number in version. - - e.g qgis version = 3.16 we add patch number (99) in versioning -> 3.16.99 - We use this versioning to query against PluginVersion min_qg_version, - so that the query result will include all PluginVersion with - minimum QGIS version 3.16 regardless of the patch number. - """ - - if not version: - return version - separator = '.' - v = version.split(separator) - if len(v) == 2: - two_first_segment = separator.join(v[:2]) - version = f'{two_first_segment}.{additional_patch}' - return version - - -@cache_page(60 * 15) -def xml_plugins(request, qg_version=None, stable_only=None, package_name=None): - """ - The XML file - - accepted parameters: - - * qgis: qgis version - * stable_only: 0/1 - * package_name: Plugin.package_name - - """ - request_version = request.GET.get("qgis", "1.8.0") - version_level = len(str(request_version).split('.')) - 1 - qg_version = ( - qg_version - if qg_version is not None - else vjust( - request_version, fillchar="0", level=version_level, force_zero=True - ) - ) - stable_only = ( - stable_only if stable_only is not None else request.GET.get("stable_only", "0") - ) - package_name = ( - package_name - if package_name is not None - else request.GET.get("package_name", None) - ) - - filters = {} - version_filters = {} - object_list = [] - - if qg_version: - filters.update({'pluginversion__min_qg_version__lte' : _add_patch_version(qg_version, '99')}) - version_filters.update({'min_qg_version__lte' : _add_patch_version(qg_version, '99')}) - filters.update({'pluginversion__max_qg_version__gte' : _add_patch_version(qg_version, '0')}) - version_filters.update({'max_qg_version__gte' : _add_patch_version(qg_version, '0')}) - - # Get all versions for the given plugin) - if package_name: - filters.update({"package_name": package_name}) - try: - plugin = Plugin.approved_objects.get(**filters) - plugin_version_filters = copy.copy(version_filters) - plugin_version_filters.update({"plugin": plugin}) - for plugin_version in PluginVersion.stable_objects.filter( - **plugin_version_filters - ): - object_list.append(plugin_version) - if stable_only != "1": - for plugin_version in PluginVersion.experimental_objects.filter( - **plugin_version_filters - ): - object_list.append(plugin_version) - except Plugin.DoesNotExist: - pass - else: - - # Checked the cached plugins - qgis_version = request.GET.get("qgis", None) - qgis_filename = "plugins_{}.xml".format(qgis_version) - folder_name = os.path.join(settings.MEDIA_ROOT, "cached_xmls") - path_file = os.path.join(folder_name, qgis_filename) - if os.path.exists(path_file): - return HttpResponse(open(path_file).read(), content_type="application/xml") - - trusted_users_ids = list( - zip( - *User.objects.filter( - Q( - user_permissions__codename="can_approve", - user_permissions__content_type__app_label="plugins", - ) - | Q(is_superuser=True) - ) - .distinct() - .values_list("id") - ) - )[0] - qs = Plugin.approved_objects.filter(**filters).annotate( - is_trusted=RawSQL( - "%s.created_by_id in (%s)" - % ( - Plugin._meta.db_table, - (",").join([str(tu) for tu in trusted_users_ids]), - ), - (), - ) - ) - for plugin in qs: - plugin_version_filters = copy.copy(version_filters) - plugin_version_filters.update({"plugin_id": plugin.pk}) - try: - data = PluginVersion.stable_objects.filter(**plugin_version_filters)[0] - setattr(data, "is_trusted", plugin.is_trusted) - object_list.append(data) - except IndexError: - pass - if stable_only != "1": - try: - data = PluginVersion.experimental_objects.filter( - **plugin_version_filters - )[0] - setattr(data, "is_trusted", plugin.is_trusted) - object_list.append(data) - except IndexError: - pass - - return render( - request, - "plugins/plugins.xml", - {"object_list": object_list}, - content_type="text/xml", - ) - - -@cache_page(60 * 15) -def xml_plugins_new(request, qg_version=None, stable_only=None, package_name=None): - """ - The XML file - - accepted parameters: - - * qgis: qgis version - * stable_only: 0/1 - * package_name: Plugin.package_name - - """ - request_version = request.GET.get("qgis", "1.8.0") - version_level = len(str(request_version).split('.')) - 1 - qg_version = ( - qg_version - if qg_version is not None - else vjust( - request_version, fillchar="0", level=version_level, force_zero=True - ) - ) - stable_only = ( - stable_only if stable_only is not None else request.GET.get("stable_only", "0") - ) - package_name = ( - package_name - if package_name is not None - else request.GET.get("package_name", None) - ) - - filters = {} - version_filters = {} - object_list = [] - - if qg_version: - filters.update({'pluginversion__min_qg_version__lte' : _add_patch_version(qg_version, '99')}) - version_filters.update({'min_qg_version__lte' : _add_patch_version(qg_version, '99')}) - filters.update({'pluginversion__max_qg_version__gte' : _add_patch_version(qg_version, '0')}) - version_filters.update({'max_qg_version__gte' : _add_patch_version(qg_version, '0')}) - - # Get all versions for the given plugin - if package_name: - filters.update({"package_name": package_name}) - try: - plugin = Plugin.approved_objects.get(**filters) - plugin_version_filters = copy.copy(version_filters) - plugin_version_filters.update({"plugin": plugin}) - for plugin_version in PluginVersion.stable_objects.filter( - **plugin_version_filters - ): - object_list.append(plugin_version) - if stable_only != "1": - for plugin_version in PluginVersion.experimental_objects.filter( - **plugin_version_filters - ): - object_list.append(plugin_version) - except Plugin.DoesNotExist: - pass - object_list_new = object_list - else: - - # Fast lane: uses raw queries - - trusted_users_ids = """ - (SELECT DISTINCT "auth_user"."id" - FROM "auth_user" - LEFT OUTER JOIN "auth_user_user_permissions" - ON ( "auth_user"."id" = "auth_user_user_permissions"."user_id" ) - LEFT OUTER JOIN "auth_permission" - ON ( "auth_user_user_permissions"."permission_id" = "auth_permission"."id" ) - LEFT OUTER JOIN "django_content_type" - ON ( "auth_permission"."content_type_id" = "django_content_type"."id" ) - WHERE (("auth_permission"."codename" = 'can_approve' - AND "django_content_type"."app_label" = 'plugins') - OR "auth_user"."is_superuser" = True)) - """ - - sql = """ - SELECT DISTINCT ON (pv.plugin_id) pv.*, - pv.created_by_id IN %(trusted_users_ids)s AS is_trusted - FROM %(pv_table)s pv - WHERE ( - pv.approved = True - AND pv."max_qg_version" >= '%(qg_version_with_patch_0)s' - AND pv."min_qg_version" <= '%(qg_version_with_patch_99)s' - AND pv.experimental = %(experimental)s - ) - ORDER BY pv.plugin_id, pv.version DESC - """ - - object_list_new = PluginVersion.objects.raw( - sql - % { - "pv_table": PluginVersion._meta.db_table, - "p_table": Plugin._meta.db_table, - "qg_version": qg_version, - "qg_version_with_patch_0": _add_patch_version(qg_version, '0'), - "qg_version_with_patch_99": _add_patch_version(qg_version, '99'), - "experimental": "False", - "trusted_users_ids": str(trusted_users_ids), - } - ) - - object_list_new = PluginVersion.objects.raw(sql % { - 'pv_table': PluginVersion._meta.db_table, - 'p_table': Plugin._meta.db_table, - 'qg_version_with_patch_0': _add_patch_version(qg_version, '0'), - 'qg_version_with_patch_99': _add_patch_version(qg_version, '99'), - 'experimental': 'False', - 'trusted_users_ids': str(trusted_users_ids), - }) - - - if stable_only != '1': - # Do the query - object_list_new = [o for o in object_list_new] - object_list_new += [o for o in PluginVersion.objects.raw(sql % { - 'pv_table': PluginVersion._meta.db_table, - 'p_table': Plugin._meta.db_table, - 'qg_version_with_patch_0': _add_patch_version(qg_version, '0'), - 'qg_version_with_patch_99': _add_patch_version(qg_version, '99'), - 'experimental': 'True', - 'trusted_users_ids': str(trusted_users_ids), - })] - - return render( - request, - "plugins/plugins.xml", - {"object_list": object_list_new}, - content_type="text/xml", - ) diff --git a/qgis-app/settings.py b/qgis-app/settings.py index e24d7767..eefe41b7 100644 --- a/qgis-app/settings.py +++ b/qgis-app/settings.py @@ -80,7 +80,7 @@ "django.contrib.auth.middleware.AuthenticationMiddleware", "django.contrib.messages.middleware.MessageMiddleware", # Needed by rpc4django - "plugins.middleware.HttpAuthMiddleware", + "base.middleware.HttpAuthMiddleware", "django.contrib.auth.middleware.RemoteUserMiddleware", "django.contrib.flatpages.middleware.FlatpageFallbackMiddleware", # Added by Tim for advanced loggin options @@ -115,8 +115,6 @@ # Uncomment the next line to enable admin documentation: # 'django.contrib.admindocs', "django.contrib.staticfiles", - # ABP: - "plugins", #'pagination', "django.contrib.humanize", #'django.contrib.markup', @@ -148,7 +146,6 @@ "djangoratings", "lib", "endless_pagination", - "userexport", "bootstrap_pagination", "sortable_listview", "user_map", @@ -336,11 +333,6 @@ "debug_toolbar.panels.redirects.RedirectsPanel", ] -BROKER_URL = "amqp://guest:guest@%s:5672//" % os.environ["RABBITMQ_HOST"] -RESULT_BACKEND = BROKER_URL -CELERY_BROKER_URL = BROKER_URL -CELERY_RESULT_BACKEND = CELERY_BROKER_URL - GEOIP_PATH='/var/opt/maxmind/' # Token access and refresh validity SIMPLE_JWT = { diff --git a/qgis-app/settings_docker.py b/qgis-app/settings_docker.py index 5dd7c061..726a74e2 100644 --- a/qgis-app/settings_docker.py +++ b/qgis-app/settings_docker.py @@ -1,5 +1,3 @@ -from celery.schedules import crontab - from settings import * import ast import os @@ -53,8 +51,6 @@ "django.contrib.flatpages", # full text search postgres "django.contrib.postgres", - # ABP: - "plugins", "django.contrib.humanize", "django.contrib.syndication", "bootstrap_pagination", @@ -69,7 +65,6 @@ "simplemenu", "tinymce", "rpc4django", - "feedjack", "preferences", "rest_framework", 'rest_framework.authtoken', @@ -109,7 +104,7 @@ PAGINATION_DEFAULT_PAGINATION_HUB = 30 LOGIN_REDIRECT_URL = "/" SERVE_STATIC_MEDIA = DEBUG -DEFAULT_PLUGINS_SITE = os.environ.get("DEFAULT_PLUGINS_SITE", "https://plugins.qgis.org/") +DEFAULT_HUB_SITE = os.environ.get("DEFAULT_HUB_SITE", "https://hub.qgis.org/") # See fig.yml file for postfix container definition # @@ -125,7 +120,7 @@ EMAIL_HOST_USER = os.environ.get("EMAIL_HOST_USER", "automation") EMAIL_HOST_PASSWORD = os.environ.get("EMAIL_HOST_PASSWORD", "docker") EMAIL_USE_TLS = ast.literal_eval(os.environ.get("EMAIL_USE_TLS", "False")) -EMAIL_SUBJECT_PREFIX = os.environ.get("EMAIL_SUBJECT_PREFIX", "[QGIS Plugins]") +EMAIL_SUBJECT_PREFIX = os.environ.get("EMAIL_SUBJECT_PREFIX", "[QGIS Hub]") # django uploaded file permission FILE_UPLOAD_PERMISSIONS = 0o644 @@ -139,32 +134,7 @@ "METABASE_DOWNLOAD_STATS_URL", "/metabase" ) -CELERY_RESULT_BACKEND = 'rpc://' -CELERY_BROKER_URL = os.environ.get('BROKER_URL', 'amqp://rabbitmq:5672') -CELERY_BEAT_SCHEDULE = { - 'generate_plugins_xml': { - 'task': 'plugins.tasks.generate_plugins_xml.generate_plugins_xml', - 'schedule': crontab(minute='*/10'), # Execute every 10 minutes. - 'kwargs': { - 'site': DEFAULT_PLUGINS_SITE - } - }, - 'update_feedjack': { - 'task': 'plugins.tasks.update_feedjack.update_feedjack', - 'schedule': crontab(minute='*/30'), # Execute every 30 minutes. - }, - 'update_qgis_versions': { - 'task': 'plugins.tasks.update_qgis_versions.update_qgis_versions', - 'schedule': crontab(minute='*/30'), # Execute every 30 minutes. - }, - # Index synchronization sometimes fails when deleting - # a plugin and None is listed in the search list. So I think - # it would be better if we rebuild the index frequently - 'rebuild_search_index': { - 'task': 'plugins.tasks.rebuild_search_index.rebuild_search_index', - 'schedule': crontab(minute=0, hour=3), # Execute every day at 3 AM. - } -} + # Set plugin token access and refresh validity to a very long duration SIMPLE_JWT = { 'ACCESS_TOKEN_LIFETIME': timedelta(days=365*1000), diff --git a/qgis-app/settings_local.py.templ b/qgis-app/settings_local.py.templ deleted file mode 100644 index 8b604a68..00000000 --- a/qgis-app/settings_local.py.templ +++ /dev/null @@ -1,88 +0,0 @@ -ADMINS = ( - ('ElPaso', 'elpaso@itopen.it'), -) - -MANAGERS = ADMINS -# Tell django which clients may receive debug messages...used by django-debug-toolbar -INTERNAL_IPS = ('127.0.0.1','') - -# Disable for prod machine -DEBUG = True -TEMPLATE_DEBUG = DEBUG -LOGGING_OUTPUT_ENABLED=DEBUG -LOGGING_LOG_SQL=DEBUG - - -ADMINS = ( - # ('Your Name', 'your_email@domain.com'), -) - -DATABASES = { - 'default': { - # Newer django versions may require you to use the postgis backed - - #'ENGINE': 'django.contrib.gis.db.backends.postgis', # Add 'postgresql_psycopg2', 'postgresql', 'mysql', 'sqlite3' or 'oracle'. - 'ENGINE': 'django.db.backends.sqlite3', # Add 'postgresql_psycopg2', 'postgresql', 'mysql', 'sqlite3' or 'oracle'. - 'NAME': 'qgis-django-plugins.db', # Or path to database file if using sqlite3. - 'USER': '', # Not used with sqlite3. - 'PASSWORD': '', # Not used with sqlite3. - 'HOST': '', # Set to empty string for localhost. Not used with sqlite3. - 'PORT': '', # Set to empty string for default. Not used with sqlite3. - } -} - -#Tim for google maps in user community page -#qgis-django.localhost -GOOGLE_API_KEY='ABQIAAAAyZw9WlHOs4CazzwUByOgZxQok5WFiNcwymBq4ClbhSeQY6fSMhTl0KHT2Donh18dLk3P4AC4ddOarA' - -PAGINATION_DEFAULT_PAGINATION=20 - -# ABP: More portable config -import os -SITE_ROOT = os.path.dirname(os.path.realpath(__file__)) - -# Absolute path to the directory that holds media. -# Example: "/home/media/media.lawrence.com/" -MEDIA_ROOT = SITE_ROOT + '/static/' - -# URL that handles the media served from MEDIA_ROOT. Make sure to use a -# trailing slash if there is a path component (optional in other cases). -# Examples: "http://media.lawrence.com", "http://example.com/media/" -MEDIA_URL = 'http://localhost:8000/static/' - -SERVE_STATIC_MEDIA = True - -# TIM: Place where search indexes are stored for snippets - should be non web accessible -HAYSTACK_WHOOSH_PATH = '/home/web/qgis-django/search-index' - -# Tim Email settings -EMAIL_HOST = 'localhost' -#EMAIL_PORT = -DEFAULT_FROM_EMAIL = os.environ.get("EMAIL_HOST_USER", "automation") - -INSTALLED_APPS = [ - 'django.contrib.auth', - 'django.contrib.contenttypes', - 'django.contrib.sessions', - 'django.contrib.sites', - 'django.contrib.messages', - # Uncomment the next line to enable the admin: - 'django.contrib.admin', - # Uncomment the next line to enable admin documentation: - # 'django.contrib.admindocs', - 'django.contrib.staticfiles', - 'django.contrib.flatpages', - - # ABP: - 'plugins', - 'django.contrib.humanize', - 'django.contrib.syndication', - - 'taggit', - 'taggit_autosuggest', - 'taggit_templatetags', - #'haystack', - 'simplemenu', - 'tinymce', - 'rpc4django', -] diff --git a/qgis-app/settings_local_vagrant.py b/qgis-app/settings_local_vagrant.py deleted file mode 100644 index 2d570d59..00000000 --- a/qgis-app/settings_local_vagrant.py +++ /dev/null @@ -1,194 +0,0 @@ -import os - -SITE_ROOT = os.path.dirname(os.path.realpath(__file__)) - -DEBUG = True -THUMBNAIL_DEBUG = DEBUG # sorl.thumbnail verbose debug - -ALLOWED_HOSTS = ["*"] - -# Local time zone for this installation. Choices can be found here: -# http://en.wikipedia.org/wiki/List_of_tz_zones_by_name -# although not all choices may be available on all operating systems. -# On Unix systems, a value of None will cause Django to use the same -# timezone as the operating system. -# If running in a Windows environment this must be set to the same as your -# system time zone. -TIME_ZONE = "America/Chicago" - -# Language code for this installation. All choices can be found here: -# http://www.i18nguy.com/unicode/language-identifiers.html -LANGUAGE_CODE = "en-us" - -SITE_ID = 1 - -# If you set this to False, Django will make some optimizations so as not -# to load the internationalization machinery. -USE_I18N = True - -# If you set this to False, Django will not format dates, numbers and -# calendars according to the current locale -USE_L10N = True - -# Absolute path to the directory that holds media. -# Example: "/home/media/media.lawrence.com/" - -# Override assets for Vagrant -# User uploaded files -MEDIA_ROOT = "/home/dimas/Documents/Kartoza/QGIS-Django/vagrant_static/" - -# URL that handles the media served from MEDIA_ROOT. Make sure to use a -# trailing slash if there is a path component (optional in other cases). -# Examples: "http://media.lawrence.com", "http://example.com/media/" -MEDIA_URL = "/static/" -MEDIA_URL_FOLDER = "/static/" - -# URL prefix for admin media -- CSS, JavaScript and images. Make sure to use a -# trailing slash. -# Examples: "http://foo.com/media/", "/media/". -ADMIN_MEDIA_PREFIX = "/admin/" - -STATIC_URL = "/static_media/" -STATIC_ROOT = SITE_ROOT + "/static_media/" - - -# Make this unique, and don't share it with anybody. -SECRET_KEY = "y2vu=4qarl)p=g_blq_c4afk!p6u_cor1gy1k@05ro=+tf7+)g" - -# List of callables that know how to import templates from various sources. -TEMPLATE_LOADERS = ( - "django.template.loaders.filesystem.Loader", - "django.template.loaders.app_directories.Loader", - "django.template.loaders.eggs.Loader", # Tim: needed on live server for CAB -) - -MIDDLEWARE = [ - "django.middleware.security.SecurityMiddleware", - "django.middleware.cache.UpdateCacheMiddleware", - "django.middleware.common.CommonMiddleware", - "django.contrib.sessions.middleware.SessionMiddleware", - "django.middleware.csrf.CsrfViewMiddleware", - "django.contrib.auth.middleware.AuthenticationMiddleware", - "django.contrib.messages.middleware.MessageMiddleware", - # Needed by rpc4django - "plugins.middleware.HttpAuthMiddleware", - "django.contrib.auth.middleware.RemoteUserMiddleware", - "django.contrib.flatpages.middleware.FlatpageFallbackMiddleware", - # Added by Tim for advanced loggin options - "django.middleware.cache.FetchFromCacheMiddleware", - "middleware.XForwardedForMiddleware", -] - -ROOT_URLCONF = "urls" - -TEMPLATE_DIRS = ( - # Put strings here, like "/home/html/django_templates" or "C:/www/django/templates". - # Always use forward slashes, even on Windows. - # Don't forget to use absolute paths, not relative paths. - os.path.join(SITE_ROOT, "templates"), -) - - -STATICFILES_DIRS = [ - os.path.join(SITE_ROOT, "static"), -] - -TEMPLATES = [ - { - "BACKEND": "django.template.backends.django.DjangoTemplates", - "DIRS": ["templates"], - "APP_DIRS": True, - "OPTIONS": { - "context_processors": ( - "django.contrib.auth.context_processors.auth", - "django.contrib.messages.context_processors.messages", - "django.template.context_processors.request", - # ABP: adds DEBUG and BASE_TEMPLATE vars - "qgis_context_processor.additions", - ), - }, - }, -] - - -ADMINS = (("Admin", "admin@email.com"),) - -MANAGERS = ADMINS -# Tell django which clients may receive debug messages...used by django-debug-toolbar -INTERNAL_IPS = ("127.0.0.1", "") - -# Disable for prod machine -TEMPLATE_DEBUG = DEBUG -LOGGING_OUTPUT_ENABLED = DEBUG -LOGGING_LOG_SQL = DEBUG - -DATABASES = { - "default": { - # Newer django versions may require you to use the postgis backed - #'ENGINE': 'django.contrib.gis.db.backends.postgis', # Add 'postgresql_psycopg2', 'postgresql', 'mysql', 'sqlite3' or 'oracle'. - #'ENGINE': 'django.db.backends.sqlite3', # Add 'postgresql_psycopg2', 'postgresql', 'mysql', 'sqlite3' or 'oracle'. - "ENGINE": "django.db.backends.postgresql_psycopg2", # Add 'postgresql_psycopg2', 'postgresql', 'mysql', 'sqlite3' or 'oracle'. - #'NAME': 'qgis-django-plugins.db', # Or path to database file if using sqlite3. - "NAME": "qgis_django", # Or path to database file if using sqlite3. - "USER": "qgis_django", # Not used with sqlite3. - "PASSWORD": "qgis_django", # Not used with sqlite3. - "HOST": "127.0.0.1", # Set to empty string for localhost. Not used with sqlite3. - "PORT": "5432", # Set to empty string for default. Not used with sqlite3. - } -} - -# Tim for google maps in user community page -# qgis-django.localhost -GOOGLE_API_KEY = "ABQIAAAAyZw9WlHOs4CazzwUByOgZxQok5WFiNcwymBq4ClbhSeQY6fSMhTl0KHT2Donh18dLk3P4AC4ddOarA" - -PAGINATION_DEFAULT_PAGINATION = 5 -LOGIN_REDIRECT_URL = "/" -SERVE_STATIC_MEDIA = DEBUG - -# TIM: Place where search indexes are stored for snippets - should be non web accessible -HAYSTACK_CONNECTIONS = { - "default": { - "ENGINE": "haystack.backends.whoosh_backend.WhooshEngine", - "PATH": os.path.join(os.path.dirname(__file__), "whoosh_index"), - }, -} - -# Tim Email settings -EMAIL_HOST = "localhost" -# EMAIL_PORT = -DEFAULT_FROM_EMAIL = os.environ.get("EMAIL_HOST_USER", "automation") - -INSTALLED_APPS = [ - "django.contrib.auth", - "django.contrib.contenttypes", - "django.contrib.sessions", - "django.contrib.sites", - "django.contrib.messages", - # Uncomment the next line to enable the admin: - "django.contrib.admin", - # Uncomment the next line to enable admin documentation: - # 'django.contrib.admindocs', - "django.contrib.staticfiles", - "django.contrib.flatpages", - # ABP: - "plugins", - "django.contrib.humanize", - "django.contrib.syndication", - "bootstrap_pagination", - "sortable_listview", - "lib", # Container for small tags and functions - "sorl.thumbnail", - "djangoratings", - "taggit", - "taggit_autosuggest", - "taggit_templatetags", - "haystack", - "simplemenu", - "tinymce", - "rpc4django", -] - -PLUGIN_MAX_UPLOAD_SIZE = 1024 * 1024 * 20 - -if DEBUG: - INSTALLED_APPS.append("django_extensions") diff --git a/qgis-app/static/feedjack/default/img/button-atom.png b/qgis-app/static/feedjack/default/img/button-atom.png deleted file mode 100644 index 420c21b1..00000000 Binary files a/qgis-app/static/feedjack/default/img/button-atom.png and /dev/null differ diff --git a/qgis-app/static/feedjack/default/img/button-css.png b/qgis-app/static/feedjack/default/img/button-css.png deleted file mode 100644 index 706325e1..00000000 Binary files a/qgis-app/static/feedjack/default/img/button-css.png and /dev/null differ diff --git a/qgis-app/static/feedjack/default/img/button-django.png b/qgis-app/static/feedjack/default/img/button-django.png deleted file mode 100644 index a1578cf7..00000000 Binary files a/qgis-app/static/feedjack/default/img/button-django.png and /dev/null differ diff --git a/qgis-app/static/feedjack/default/img/button-foaf.png b/qgis-app/static/feedjack/default/img/button-foaf.png deleted file mode 100644 index df4d80eb..00000000 Binary files a/qgis-app/static/feedjack/default/img/button-foaf.png and /dev/null differ diff --git a/qgis-app/static/feedjack/default/img/button-hacker.png b/qgis-app/static/feedjack/default/img/button-hacker.png deleted file mode 100644 index 5a2e1850..00000000 Binary files a/qgis-app/static/feedjack/default/img/button-hacker.png and /dev/null differ diff --git a/qgis-app/static/feedjack/default/img/button-opml.png b/qgis-app/static/feedjack/default/img/button-opml.png deleted file mode 100644 index cf7ff013..00000000 Binary files a/qgis-app/static/feedjack/default/img/button-opml.png and /dev/null differ diff --git a/qgis-app/static/feedjack/default/img/button-rss.png b/qgis-app/static/feedjack/default/img/button-rss.png deleted file mode 100644 index b036f715..00000000 Binary files a/qgis-app/static/feedjack/default/img/button-rss.png and /dev/null differ diff --git a/qgis-app/static/feedjack/default/img/button-xhtml.png b/qgis-app/static/feedjack/default/img/button-xhtml.png deleted file mode 100644 index ec686442..00000000 Binary files a/qgis-app/static/feedjack/default/img/button-xhtml.png and /dev/null differ diff --git a/qgis-app/static/feedjack/default/img/faces/Linfiniti.png b/qgis-app/static/feedjack/default/img/faces/Linfiniti.png deleted file mode 100644 index fcd22aad..00000000 Binary files a/qgis-app/static/feedjack/default/img/faces/Linfiniti.png and /dev/null differ diff --git a/qgis-app/static/feedjack/default/img/faces/nobody.png b/qgis-app/static/feedjack/default/img/faces/nobody.png deleted file mode 100644 index 2b1328b4..00000000 Binary files a/qgis-app/static/feedjack/default/img/faces/nobody.png and /dev/null differ diff --git a/qgis-app/static/feedjack/default/img/faces/spatialgalaxy.net.png b/qgis-app/static/feedjack/default/img/faces/spatialgalaxy.net.png deleted file mode 100644 index 31ea2bc7..00000000 Binary files a/qgis-app/static/feedjack/default/img/faces/spatialgalaxy.net.png and /dev/null differ diff --git a/qgis-app/static/feedjack/default/img/faces/underdark.png b/qgis-app/static/feedjack/default/img/faces/underdark.png deleted file mode 100644 index 39cbf7a7..00000000 Binary files a/qgis-app/static/feedjack/default/img/faces/underdark.png and /dev/null differ diff --git a/qgis-app/static/feedjack/default/img/feed.png b/qgis-app/static/feedjack/default/img/feed.png deleted file mode 100644 index 6e7b676b..00000000 Binary files a/qgis-app/static/feedjack/default/img/feed.png and /dev/null differ diff --git a/qgis-app/static/feedjack/default/style.css b/qgis-app/static/feedjack/default/style.css deleted file mode 100644 index 0f853777..00000000 --- a/qgis-app/static/feedjack/default/style.css +++ /dev/null @@ -1,312 +0,0 @@ -/* - * Feedjack LostWoods theme - ************************** - * Simple and green (where's the brown? -brown doesn't count) - * - * Copyright Diego Escalante Urrelo - * - */ -body { - font-size: 0.8em; - font-family: verdana; - margin: 0; -} -div { - /*border: 1px solid blue; - padding: 5px; - margin: 5px;*/ -} -/* - * Structure - */ -#head { - height: 90px; - border-bottom: 10px outset #6E9C60; - padding: 5px; - margin-bottom: 10px; - color: white; - background-color: #6E9C60; -} -#logo { - padding-top: 3px; - padding-left: 1em; -} -#tags { - overflow: auto; - padding: 5px; - text-align: right; -} -#paginate { - margin-bottom: 15px; - margin-left: 0px; - margin-top: 5px; - padding-left: 0; - text-align: left; - vertical-align: middle; - float: left; - width: 60%; - color: black; -} -#buttons { - text-align: right; - float: right; - vertical-align: middle; - color: #aaa; - width: 40%; - line-height: 1.7em; - padding:0; - padding-bottom: 10px; -} -#usertags { - clear: both; - margin: 5px; - padding: 5px; - width: 75%; - text-align: center; -} -#content { - clear : both; - width: 75%; -} -#sidebar { - width: 20%; - position: absolute; - right:0; - top: 170px; - border-left: 10px outset #6E9C60; - padding-left: 10px; - padding-right: 10px; -} -div.date { - font-size: x-large; - text-align: center; - font-style: italic; - padding: 5px; - color: black; - border: 2px solid #6E9C60; - border-right: 0; - border-left: 0; - margin-bottom: 10px; - -} -/* - * Post structure - */ -div.post { - overflow: auto; - margin-bottom: 50px; - padding-bottom: 30px; - border-bottom: 12px solid #6E9C60; - border-right: 1px inset #6E9C60; - padding-right: 10px; -} -div.avatar { - float: right; - width: 15%; - text-align: center; -} -div.post-title { - text-align: left; - - font-size: 180%; - font-family: trebuchet ms; - font-weight: bold; - padding: 5px; - width: 75%; -} -div.post-content { - width: 72%; - overflow: auto; - text-align: justify; - font-size: 90%; - line-height: 1.8em; - padding-left: 4em; - padding-right: 4em; - border-right: 1px dotted #ccc; -} -div.post-content li { - margin: 0; - padding: 0; - line-height: 130%; -} -div.post-content table{ - border: 0; - margin-left: 50px; - -} -div.post-content td { - border: 2px solid #ccc; -} -div.post-meta { - color: #666; - margin-top: 20px; - border-top: 1px solid #ccc; - width: 100%; - text-align: right; -} -div.tags { - margin-top: 10px; -} - -/* - * Elements - */ -blockquote { - color: #777; - margin: 15px 30px 0px 10px; - padding: 20px; - border: 1px solid #ddd; - border-left: 7px solid #ddd; - -} -a:link { - color: #4C6B46; -} -a:hover { - color: #33408A; -} -h1 a:link { - text-decoration: none; - color: inherit; -} -#buttons img { - vertical-align: middle; -} -#head h1 { - font-style: italic; - font-size: xx-large; - border-bottom: 3px solid #6E9C60; - margin-bottom: 5px; - margin-top: 10px; -} -#head a:link, a:visited, #head a:active { - color: inherit; -} -.love_feedjack { - font-size: 145%; - font-weight: bold; - font-style: italic; -} -.cloud_1 { - font-size: 50%; -} -.cloud_2 { - font-size: 100%; -} -.cloud_3 { - font-size: 120%; - font-weight: bold; -} -.cloud_4 { - font-size: 140%; - font-weight: bold; -} -.cloud_5 { - font-size: 160%; - font-weight: bold; -} -#paginate ul { - margin:0; - padding:0; -} -#paginate li { - display: inline; - margin: 2px; - padding: 10px; - background-color: #fbfbfb; - border: 2px solid #ddd; - line-height: 1.7em; - margin-left: 3px; - margin-right: 3px; - text-align: center; - vertical-align: middle; -} -#paginate li.tagname { - font-weight: bold; - font-size: 140%; -} -#paginate a:link { - color: #4C6B46; -} -img { - border: 0; -} -#sidebar ul { - list-style-type: none; - padding: 0; - margin: 0; -} -#sidebar li { - display: block; - clear: both; - line-height: 25px; -} -#sidebar a.nombre { - display: inline; - color: #33408A; - vertical-align: middle; - margin-left: 2px; - margin-right: 2px; -} -#sidebar img.face { - vertical-align: middle; - margin-right: 5px; -} -#sidebar h4 { - font-style: italic; - border-bottom: 1px solid #ccc; -} -#sidebar ul.suscriptores { - /* - background-image: url('enchufe.png'); - background-repeat: repeat-y; - background-position: center top; - */ -} -#sidebar h4 + p { - text-align: justify; -} -#tags ul, #usertags ul { - background-color: #fbfbfb; - padding: 5px; - border: 2px solid #ddd; - margin:0; - text-align: center; -} -#tags li, #usertags li { - display: inline; - margin: 0; - line-height: 1.7em; - margin-left: 3px; - margin-right: 3px; -} -#tags a:link, #usertags a:link { - color: #4C6B46; -} -span.name { - color: #333; -} -span.nick { - color: #555; -} -span.url a { - color: #bbb; -} -span.url a:hover { - color: #777; -} - -div.tags ul { - background-color: #fbfbfb; - padding: 5px; - border: 2px solid #ddd; - margin:0; - text-align: center; -} -div.tags li { - display: inline; - margin: 0; - line-height: 1.7em; - margin-left: 3px; - margin-right: 3px; -} diff --git a/qgis-app/static/feedjack/sinx/default.css b/qgis-app/static/feedjack/sinx/default.css deleted file mode 100644 index ccd69bba..00000000 --- a/qgis-app/static/feedjack/sinx/default.css +++ /dev/null @@ -1,52 +0,0 @@ -/* - * A List Apart's Holy Grail layout - * http://www.alistapart.com/articles/holygrail - */ - -@import url(posts.css); -@import url(theme.css); - -body { - min-width: 500px; - padding:0; margin:0; -} - -#header { - top: 0; - margin: 0; -} - -#container { - padding-left: 5%; - padding-right: 25%; -} - -#container .column { - position: relative; - float: left; -} - -#center { - width: 100%; -} - -#left { - width: 5%; - right: 5%; - margin-left: -100%; -} - -#right { - width: 25%; - margin-right: -100%; - padding: 5px; -} - -#footer { - clear: both; -} - -/*** IE6 Fix ***/ -* html #left { - left: 5%; -} diff --git a/qgis-app/static/feedjack/sinx/dot.png b/qgis-app/static/feedjack/sinx/dot.png deleted file mode 100644 index ddabb7c7..00000000 Binary files a/qgis-app/static/feedjack/sinx/dot.png and /dev/null differ diff --git a/qgis-app/static/feedjack/sinx/fade.png b/qgis-app/static/feedjack/sinx/fade.png deleted file mode 100644 index d3bd7e8c..00000000 Binary files a/qgis-app/static/feedjack/sinx/fade.png and /dev/null differ diff --git a/qgis-app/static/feedjack/sinx/h2_bg.png b/qgis-app/static/feedjack/sinx/h2_bg.png deleted file mode 100644 index 84071997..00000000 Binary files a/qgis-app/static/feedjack/sinx/h2_bg.png and /dev/null differ diff --git a/qgis-app/static/feedjack/sinx/img/css.png b/qgis-app/static/feedjack/sinx/img/css.png deleted file mode 100644 index 706325e1..00000000 Binary files a/qgis-app/static/feedjack/sinx/img/css.png and /dev/null differ diff --git a/qgis-app/static/feedjack/sinx/img/django_80x15.png b/qgis-app/static/feedjack/sinx/img/django_80x15.png deleted file mode 100644 index 0b2408b4..00000000 Binary files a/qgis-app/static/feedjack/sinx/img/django_80x15.png and /dev/null differ diff --git a/qgis-app/static/feedjack/sinx/img/faces/nobody.png b/qgis-app/static/feedjack/sinx/img/faces/nobody.png deleted file mode 100644 index 2b1328b4..00000000 Binary files a/qgis-app/static/feedjack/sinx/img/faces/nobody.png and /dev/null differ diff --git a/qgis-app/static/feedjack/sinx/img/hacker.png b/qgis-app/static/feedjack/sinx/img/hacker.png deleted file mode 100644 index 5a2e1850..00000000 Binary files a/qgis-app/static/feedjack/sinx/img/hacker.png and /dev/null differ diff --git a/qgis-app/static/feedjack/sinx/img/rss20.png b/qgis-app/static/feedjack/sinx/img/rss20.png deleted file mode 100644 index 93c1dec9..00000000 Binary files a/qgis-app/static/feedjack/sinx/img/rss20.png and /dev/null differ diff --git a/qgis-app/static/feedjack/sinx/img/xhtml.png b/qgis-app/static/feedjack/sinx/img/xhtml.png deleted file mode 100644 index a0df12bc..00000000 Binary files a/qgis-app/static/feedjack/sinx/img/xhtml.png and /dev/null differ diff --git a/qgis-app/static/feedjack/sinx/posts.css b/qgis-app/static/feedjack/sinx/posts.css deleted file mode 100644 index ea085dff..00000000 --- a/qgis-app/static/feedjack/sinx/posts.css +++ /dev/null @@ -1,151 +0,0 @@ - -h1, h2, h3, h4 { - clear: both; -} - -acronym, abbr { - border-bottom: 1px dashed #333; -} - -acronym, abbr, span.caps { - cursor: help; - letter-spacing: .07em; -} - -blockquote { - margin-left: 2em; - border: 1px solid #00B; - border-left-width: 3px; - font-style: italic; - padding: 10px; - clear: both; - background: #FFFFE0; -} - -cite { - font-style: normal; -} - -#center h1 { - text-align: center; - border: 2px solid #27408B; - border-width: 2px 0; - margin-top: 1em; - font-weight: normal; -} - -#center h2 { - background: #C3C3C3 url(h2_bg.png) repeat-x bottom; - padding: 5px; - border: 3px solid #666; - margin-top: 1em; -} - -#center h2 a { - display: block; - color: #666; - text-decoration: none; -} -#center h2 a:hover { - text-decoration: underline; - background: none; -} - -#center h3 { - text-align: center; - border-bottom: 1px solid #CCC; -} - -#center h3 a { - text-decoration: none; - color: #000; - font-size: 160%; -} - -#center h3 a:hover { - background: none; - color: #644; - text-decoration: underline; -} - -#center .entry { - width: 80%; - border-left: 1px solid #DDD; - padding: 10px; - float: right; -} - -.entry { - text-align: justify; - overflow: auto; -} -.entry li { - list-style-image: url('dot.png'); - margin-left: 1em; -} - -.entry img { - max-width: 95%; - height: auto; - padding: 2px; - border: 2px solid #224; - margin: 5px; -} - -.content h3, .content h4 { - text-align: left; - border: 0; -} - -.centrado, .center, .centrar { - text-align: center; -} -.alignleft, .align-left, .izquierda, .izq, .left { - float: left; -} -.alignright, .align-right, .derecha, .der, .right { - float: right; -} - -.entry pre { - width: 85%; - padding: 10px; - border: 1px solid #BBB; - overflow: auto; -} - -.entry pre.code { - border-color: 1px solid #00A; - color: #00A; -} - -#center div.face { - float: left; - width: 14%; -} -#center div.face p { - color: #AAA; - font-size: 70%; - white-space: normal; -} -#center div.face img { - display: block; - max-width: 100%; - width: auto; - height: auto; -} - -.content table { - max-width: 95; -} - -div.ttag { - clear:both; - color:#BBB; - text-align:right; - font-family:monospace; -} -div.ttag img { border:0;margin:0;padding:0; } -div.ttag a { color:#BBB; } -div.ttag a:hover { color:#999;} - diff --git a/qgis-app/static/feedjack/sinx/smiley.png b/qgis-app/static/feedjack/sinx/smiley.png deleted file mode 100644 index 2e15d0b6..00000000 Binary files a/qgis-app/static/feedjack/sinx/smiley.png and /dev/null differ diff --git a/qgis-app/static/feedjack/sinx/theme.css b/qgis-app/static/feedjack/sinx/theme.css deleted file mode 100644 index 32aafa52..00000000 --- a/qgis-app/static/feedjack/sinx/theme.css +++ /dev/null @@ -1,180 +0,0 @@ - -body { - background: #F8F8F8 url(fade.png) repeat-x scroll top left; -} - -#header { - background: #224; - border-bottom: 2px solid #CCC; - padding: 1em; -} - -#header .tit { - float: left; - width: 45%; -} - -#header .tit * { - color: #EEE; -} - -#header .tit a, #header .tit a:visited, #header .tit a:hover { - background: none; - text-decoration: none; -} - -#header .tit h1 { - display: inline; - font-style: italic; - border-bottom: 2px solid #7891DE; - padding: 0 1em; -} - -#header .tit p { - font-size: 80%; - font-style: italic; -} - -#header .googlead { - float: right; - background: #FFF; - width: 40%; - border: 1px solid #CCC; -} - -a { color: #00F; } -a:hover { color: #FFF; background: #00F; } -a:visited { color: #A0C; } -a:active { color: #F00; } - -a img { - border: 0; - padding: 1px; -} - -#right, #left { - font-size: 80%; - text-align: justify; - border-left: 1px solid #DDD; -} - -#right h2 { - border-bottom: 1px solid #BBB; - border-top: 1px solid #BBB; - padding: 2px; - text-align: right; - font-style: italic; - font-size: 110%; -} - -.suscribers p { - padding: 1px; - margin: 0; -} - -#buttons { text-align: justify; } - -#center { - padding: 10px; - color: #444; - border: 1px solid #DDD; - border-width: 0 1px; - background: #F8F8F8; -} - -#center .content a { color: #0000CD; } -#center .content a:visited { color: #6A5ACD; } -#center .content a:active { color: #CD5C5C; } -#center .content a:hover { color: #FFF; background: #0000CD; } - -.entry p.date { - color: #AAA; - font-size: 80%; - text-align: center; - clear: both; -} - -.entry p.date a { - color: #7C7CB8; -} - -.entry p.date a:hover { - color: #FFF; - background: #7C7CB8; -} - -#footer { - background: #DDD url(fade.png) repeat-x scroll top left; - border-top: 2px solid #27408B; - padding: 1em; - font-size: 80%; - color: #444; -} - -#footer .welcome{ - float: left; - width: 40%; - margin-left: 5%; -} - -#footer .credits { - float: right; - width: 40%; - margin-right: 5%; -} - -#footer p.paginator{ - text-align: center; -} - -#footer p.paginator a { - padding: 3px; - color: #00C; -} - -#footer p.paginator a:hover { - background: #EEE; -} - -#footer p.paginator a:visited { - color: #00C; -} - -.planetarium { - list-style-type: none; - text-align: right; - margin:0; padding:0; -} - -#cloud { - margin: 0; - padding: 0; - white-space: normal; - text-align: justify; -} -#cloud a.cloud_5 { - font-size:200%; - color:#000; -} -#cloud a.cloud_4 { - font-size:175%; - color:#222; -} -#cloud a.cloud_3 { - font-size:150%; - color:#444; -} -#cloud a.cloud_2 { - font-size:125%; - color:#666; -} -#cloud a.cloud_1 { - font-size:80%; - color:#888; -} -#cloud a:hover { - color: #F00; - background: #FF0; -} - -.clear { clear: both; } diff --git a/qgis-app/static/style/feedjack.css b/qgis-app/static/style/feedjack.css deleted file mode 100644 index 24048905..00000000 --- a/qgis-app/static/style/feedjack.css +++ /dev/null @@ -1,42 +0,0 @@ -.date{ - color: #999; - font-size: 1.2em; - padding: 0.5em 0em 0.5em 0em; - margin: 0em 0 1em 0; -} -.avatar{ - margin: 1em 0em 1em 1em; - float: right; -} -.name{ - font-size: 2.2em; - font-weight: bold; - color: #234261; - margin: 2em 0em 0em 0em; -} - -.post-title{ - font-size: 1.4em; - font-style: italic; - font-weight: bold; - padding: 1.0em 0em 0em 0.5em; -} -#feed_list{ - margin: 2.0em 0em 0em 1em; -} -#paginate{ - margin: 2.0em 0em 0em 1em; -} -#tags{ - margin: 2.0em 0em 0em 1em; -} -.post-content{ - margin: 0em 0em 0em 1em; -} -.post-meta{ - margin: 0em 0em 3em 0em; -} - -ul, ol { - margin: 0.5em 0em 0.5em 4em; -} diff --git a/qgis-app/static/style/planet.css b/qgis-app/static/style/planet.css deleted file mode 100644 index aac306ca..00000000 --- a/qgis-app/static/style/planet.css +++ /dev/null @@ -1,216 +0,0 @@ -* { - padding: 0; - margin: 0; - font-family: "Bitstream Vera Sans", sans-serif; -} - -html { - background: #2a5075; -} - -body { - background: #f9f9f9; - -} - -h1 { - font-weight: normal; - font-size: 3.5em; - letter-spacing: -2px; - text-transform: lowercase; - text-align: right; - color: #fff; - background: #224262; - margin: 0 -230px 21px -20px; - border-top: 30px solid #e9eef1; - height: 85px; - padding-right: 240px; - padding-top: 35px; -} - -.admin { - text-align: right; -} - -.daygroup { - background: #fff; - padding: 12px; - margin-bottom: 7px; - border: 1px solid #ebeaea; - border-radius: 7px; -moz-border-radius: 7px; -} - -.daygroup h2 { - color: #2a5075; - font-weight: normal; - font-size: 1.1em; - margin-top: 7px; - margin-bottom: 7px; - -} - -.channelgroup h3 { - font-weight: normal; - font-size: 1.2em; - background-color: #dee988; - padding: 4px 12px 4px 12px; - margin-top: 14px; - margin-bottom: 14px; - border-radius: 7px; -moz-border-radius: 7px; -} - -.entry p { - font-size: 1em; -} - -.entry li { - margin-left: 21px; -} - -.entry a { - color: #224262; -} - -.entry p, ul, ol { - margin-bottom: 7px; -} - -h3 a { - text-decoration: none; - color: inherit; -} - -h4 { - font-weight: bold; -} - -h4 a { - text-decoration: none; - color: inherit; -} - -img.face { - float: right; - margin-top: -3em; -} - -.entry { - margin-bottom: 2em; -} - -.entry .date { - font-family: "Bitstream Vera Sans", sans-serif; - color: grey; -} - -.entry .date a { - text-decoration: none; - color: inherit; -} - -.sidebar { - position: absolute; - top: 10px; - right: 10px; - width: 200px; - - - - margin-left: 0px; - margin-right: 0px; - padding-right: 10px; - - padding-top: 25px; - padding-left: 0px; - - - font-size: 0.9em; -} - -.sidebar h2 { - padding-top: 21px; - font-size: 1.1em; - font-weight: bold; - color: black; -} - -.sidebar ul { - padding-left: 7px; - list-style-type: none; -} - -.sidebar ul li:hover { - color: grey; -} - -.sidebar ul li a { - text-decoration: none; -} - -.sidebar ul li a:hover { - text-decoration: underline; -} - -.sidebar ul li a img { - border: 0; -} - -.sidebar p { - margin-top: 30px; - padding-top: 10px; - - padding-left: 5px; -} - -.sidebar .message { - cursor: help; - border-bottom: 1px dashed red; -} - -.sidebar a.message:hover { - cursor: help; - background-color: #ff0000; - color: #ffffff !important; - text-decoration: none !important; -} - -a:hover { - text-decoration: underline !important; - color: blue !important; -} - -#leftcolumn ul { - padding-left: 1em; -} - -div.post-meta span.date { - font-weight: bold; -} - -div.post-meta span.name { - font-style: italic; -} - -div.post { - margin-bottom: 2em; - clear: both; -} - -div.post-title { - font-size: 200%; - margin: 0.5em 0; - line-height: 1.4em; -} - -div.avatar { - float: right; -} - -#paginate ul { - list-style-type: none; -} - -#paginate ul li { - display: inline; - margin: 0 1em; -} diff --git a/qgis-app/static/style/plugins.xsl b/qgis-app/static/style/plugins.xsl deleted file mode 100644 index 1f5550cc..00000000 --- a/qgis-app/static/style/plugins.xsl +++ /dev/null @@ -1,198 +0,0 @@ - - - - - - - -QGIS Plugins - Official Repository - - - - - - -

QGIS Python Plugins

-

-NOTE: The preferred way to install QGIS plugins is via the Plugin Manager in QGIS itself! -QGIS will download this list automatically and make it possible to install a plugin with one click. -

-

-NOTE: Here you only see a representation of the plugins working for the requested version (defined by the "?qgis=x.y.z" part of the url). -

- - - - - - -
- - -
-
- - : - -
-
- -
-
- -
-
-Tags: -
-
-Download: - - - - - - -
-
-Author: -
-
-Version: -
-
-Trusted: -
-
-Experimental: -
-
-Deprecated: -
-
-Minimum QGIS Version: -
-
-Maximum QGIS Version: -
-
-Home page: - - - - - - -
-
-Tracker: - - - - - - -
-
-Repository: - - - - - - -
- - -
-
-
- - - -
- -
diff --git a/qgis-app/plugins/management/__init__.py b/qgis-app/styles/management/__init__.py similarity index 100% rename from qgis-app/plugins/management/__init__.py rename to qgis-app/styles/management/__init__.py diff --git a/qgis-app/plugins/management/commands/__init__.py b/qgis-app/styles/management/commands/__init__.py similarity index 100% rename from qgis-app/plugins/management/commands/__init__.py rename to qgis-app/styles/management/commands/__init__.py diff --git a/qgis-app/styles/management/commands/drop_plugins_planet_tables.py b/qgis-app/styles/management/commands/drop_plugins_planet_tables.py new file mode 100644 index 00000000..c8b12efb --- /dev/null +++ b/qgis-app/styles/management/commands/drop_plugins_planet_tables.py @@ -0,0 +1,38 @@ + +from django.core.management.base import BaseCommand +from django.db import connection + +class Command(BaseCommand): + help = 'Drops planet and plugins tables from the database with confirmation' + + def handle(self, *args, **options): + # List of table prefixes to remove + table_prefixes = [ + 'feedjack_', + 'plugins_', + ] + + # Get a cursor to execute SQL commands + with connection.cursor() as cursor: + # Get the list of all tables in the database + cursor.execute(""" + SELECT tablename + FROM pg_catalog.pg_tables + WHERE schemaname = 'public'; + """) + tables = [row[0] for row in cursor.fetchall()] + + # Iterate through all tables and drop those that match the prefixes + for table in tables: + if any(table.startswith(prefix) for prefix in table_prefixes): + # Ask for user confirmation + self.stdout.write(f'Found table: {table}') + confirmation = input(f'Are you sure you want to permanently delete the table "{table}"? This action cannot be undone. Type "yes" to confirm: ') + + if confirmation.lower() == 'yes': + self.stdout.write(f'Dropping table: {table}') + cursor.execute(f"DROP TABLE IF EXISTS \"{table}\" CASCADE;") + else: + self.stdout.write(f'Skipping table: {table}') + + self.stdout.write(self.style.SUCCESS('Process completed. Tables with specified prefixes have been handled.')) \ No newline at end of file diff --git a/qgis-app/styles/models.py b/qgis-app/styles/models.py index 52e62f51..5ae2416a 100644 --- a/qgis-app/styles/models.py +++ b/qgis-app/styles/models.py @@ -5,7 +5,7 @@ from django.urls import reverse from django.utils.translation import gettext_lazy as _ -STYLES_STORAGE_PATH = getattr(settings, "PLUGINS_STORAGE_PATH", "styles/%Y") +STYLES_STORAGE_PATH = getattr(settings, "HUB_STORAGE_PATH", "styles/%Y") class StyleType(models.Model): diff --git a/qgis-app/templates/admin/auth/change_list.html b/qgis-app/templates/admin/auth/change_list.html deleted file mode 100644 index 4a149659..00000000 --- a/qgis-app/templates/admin/auth/change_list.html +++ /dev/null @@ -1,14 +0,0 @@ -{% extends "admin/change_list.html" %} -{% load i18n admin_urls %} -{% block object-tools-items %} -{{ block.super }} -
  • - {% trans "Export users" %} -
  • -
  • - {% trans "Export bad plugins list" %} -
  • -
  • - {% trans "Export plugin maintainers" %} -
  • -{% endblock %} diff --git a/qgis-app/templates/base.html b/qgis-app/templates/base.html index 74b94bd1..0f0cad5f 100644 --- a/qgis-app/templates/base.html +++ b/qgis-app/templates/base.html @@ -49,7 +49,7 @@ QGIS {% if user.is_authenticated %} - {% endif %} @@ -61,27 +61,19 @@ {% get_namedmenu Navigation as menu %}