Browse Source

Merge pull request #1105 from rafalp/master

Release 0.19.2
Rafał Pitoń 6 years ago
parent
commit
1fe0a9de47
57 changed files with 805 additions and 871 deletions
  1. 1 0
      Dockerfile
  2. 0 1
      MANIFEST.in
  3. 15 29
      README.rst
  4. 0 6
      cleansource
  5. 375 0
      dev
  6. 0 2
      docker-compose.yaml
  7. 0 0
      extras/__init__.py
  8. 0 5
      extras/config.py
  9. 0 27
      extras/createsuperuser.py
  10. 0 93
      extras/fixabsoluteimports.py
  11. 0 160
      extras/fixdictsformatting.py
  12. 0 53
      extras/fixrelativeimports.py
  13. 0 2
      extras/psql.sh
  14. 0 10
      extras/wait_for_postgres.sh
  15. 0 58
      initdev
  16. 0 8
      makemessages
  17. 1 1
      misago/__init__.py
  18. 11 24
      misago/admin/views/index.py
  19. 26 16
      misago/bin/misago-start-devproject.py
  20. 5 0
      misago/conf/debugtoolbar.py
  21. 22 13
      misago/core/management/progressbar.py
  22. 1 2
      misago/faker/management/commands/createfakebans.py
  23. 1 2
      misago/faker/management/commands/createfakecategories.py
  24. 1 2
      misago/faker/management/commands/createfakefollowers.py
  25. 5 4
      misago/faker/management/commands/createfakethreads.py
  26. 9 3
      misago/faker/management/commands/createfakeusers.py
  27. BIN
      misago/locale/en/LC_MESSAGES/django.mo
  28. 27 31
      misago/locale/en/LC_MESSAGES/django.po
  29. BIN
      misago/locale/en/LC_MESSAGES/djangojs.mo
  30. 1 1
      misago/locale/en/LC_MESSAGES/djangojs.po
  31. BIN
      misago/locale/es/LC_MESSAGES/django.mo
  32. 30 34
      misago/locale/es/LC_MESSAGES/django.po
  33. BIN
      misago/locale/fr/LC_MESSAGES/django.mo
  34. 30 34
      misago/locale/fr/LC_MESSAGES/django.po
  35. BIN
      misago/locale/ru/LC_MESSAGES/django.mo
  36. 30 34
      misago/locale/ru/LC_MESSAGES/django.po
  37. BIN
      misago/locale/tr/LC_MESSAGES/django.mo
  38. 30 34
      misago/locale/tr/LC_MESSAGES/django.po
  39. BIN
      misago/locale/zh_Hans/LC_MESSAGES/django.mo
  40. 41 45
      misago/locale/zh_Hans/LC_MESSAGES/django.po
  41. 1 1
      misago/markup/parser.py
  42. 6 6
      misago/markup/tests/test_parser.py
  43. 10 10
      misago/project_template/cron.txt
  44. 4 11
      misago/templates/misago/admin/index.html
  45. 14 8
      misago/users/activepostersranking.py
  46. 7 2
      misago/users/management/commands/buildactivepostersranking.py
  47. 26 20
      misago/users/management/commands/createsuperuser.py
  48. 0 4
      misago/users/models/user.py
  49. 0 6
      misago/users/tests/test_activepostersranking.py
  50. 1 1
      misago/users/tests/test_createsuperuser.py
  51. 13 0
      misago/users/tests/test_user_create_api.py
  52. 0 3
      pyclean
  53. 0 38
      pycodestyle.py
  54. 18 0
      requirements.in
  55. 38 18
      requirements.txt
  56. 5 7
      setup.py
  57. 0 2
      upload

+ 1 - 0
Dockerfile

@@ -4,6 +4,7 @@
 FROM python:3.5
 FROM python:3.5
 
 
 ENV PYTHONUNBUFFERED 1
 ENV PYTHONUNBUFFERED 1
+ENV IN_MISAGO_DOCKER 1
 ENV PATH "$PATH:/srv/misago"
 ENV PATH "$PATH:/srv/misago"
 
 
 # Install dependencies in one single command/layer
 # Install dependencies in one single command/layer

+ 0 - 1
MANIFEST.in

@@ -4,7 +4,6 @@ include README.rst
 include requirements.txt
 include requirements.txt
 prune devproject
 prune devproject
 prune docs
 prune docs
-prune extras
 prune testproject
 prune testproject
 graft misago
 graft misago
 global-exclude __pycache__
 global-exclude __pycache__

+ 15 - 29
README.rst

@@ -10,14 +10,14 @@ Misago
    :target: https://coveralls.io/github/rafalp/Misago?branch=master
    :target: https://coveralls.io/github/rafalp/Misago?branch=master
    :alt: Test Coverage
    :alt: Test Coverage
 
 
-.. image:: https://badges.gitter.im/Misago/Misago.svg
-   :target: https://gitter.im/Misago/Misago?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge
-   :alt: Development Chat
-
 .. image:: https://img.shields.io/badge/python-2.7%2C%203.4%2C%203.5%2C%203.6-blue.svg
 .. image:: https://img.shields.io/badge/python-2.7%2C%203.4%2C%203.5%2C%203.6-blue.svg
    :target: https://travis-ci.org/rafalp/Misago
    :target: https://travis-ci.org/rafalp/Misago
    :alt: Works on Python 2.7, 3.5, 3,6
    :alt: Works on Python 2.7, 3.5, 3,6
 
 
+.. image:: https://img.shields.io/badge/chat-on_discord-7289da.svg
+   :target: https://discord.gg/fwvrZgB
+   :alt: Community Chat
+
 
 
 **Development Status:** 🍌 `Bananas <https://en.wikipedia.org/wiki/Perpetual_beta>`_ 🍌
 **Development Status:** 🍌 `Bananas <https://en.wikipedia.org/wiki/Perpetual_beta>`_ 🍌
 
 
@@ -63,8 +63,7 @@ As of now Misago implements all features considered "must have" on live internet
 * Posts edits log allowing you to see how user messages used to look in past as well as revert function protecting you from malignant users emptying their posts contents.
 * Posts edits log allowing you to see how user messages used to look in past as well as revert function protecting you from malignant users emptying their posts contents.
 * Moderation queue for users and categories allowing you to moderate content before it becomes visible to other members of the community.
 * Moderation queue for users and categories allowing you to moderate content before it becomes visible to other members of the community.
 * Custom theme developed over bootstrap.
 * Custom theme developed over bootstrap.
-* Optionally enable users to delete their own account.
-* Anonymise user data on their account's deletion.
+* Features and settings for achieving GDPR compliance.
 
 
 Even more features will follow in future releases:
 Even more features will follow in future releases:
 
 
@@ -79,37 +78,24 @@ Even more features will follow in future releases:
 * Ranking system for forum search results based on post links, likes, author and thread importance.
 * Ranking system for forum search results based on post links, likes, author and thread importance.
 * Post reactions in place of likes.
 * Post reactions in place of likes.
 
 
+...and more!
+
 If you are looking into using Misago to run live forum, you are absolutely invited to, but please keep in mind that Misago is relatively immature software that may contain serious bugs or issues as well as quirks and lackings thay may take time to resolve, despite best efforts. 
 If you are looking into using Misago to run live forum, you are absolutely invited to, but please keep in mind that Misago is relatively immature software that may contain serious bugs or issues as well as quirks and lackings thay may take time to resolve, despite best efforts. 
 
 
 
 
 Development
 Development
 ===========
 ===========
 
 
-Preferred way to setup Misago for local development is with `Docker <https://www.docker.com/community-edition#/download>`_, which makes it easy to spin up arbitrary number of instances running different code with separate databases and dependencies one besides the other with just three terminal commands.
-
-To start, clone repository to your machine and then run following commands::
-
-   docker-compose build
-   docker-compose run --rm misago initdev
-   docker-compose up
-
-Those commands will install necessary dependencies, create new Misago project ``devproject`` that you may use for development as well as start Django developer server, enabling you to visit ``127.0.0.1:8000``
-in your browser and see the forum index. You should now be able to sign in with the superuser account, using ``Admin`` username and ``password`` password.
-
-Admin Control Panel is available under the ``127.0.0.1:8000/admincp/`` url.
-
-`manage.py` is available through Docker's `run` command::
-    
-    docker-compose run --rm misago python manage.py
+Preferred way to run Misago development instances on your machine is with `Docker <https://www.docker.com/community-edition#/download>`_, which makes it easy to spin up arbitrary number of instances running different code with separate databases and dependencies besides each other.
 
 
-Docker also allows you to run tests suite::
-    
-    docker-compose run --rm misago python runtests.py
+To start, clone the repository and run ``./dev init`` command in your terminal. This will build necessary docker containers, install python dependencies, initialize the database and create development project for you. After command does its magic, you will be able to start development server using the ``docker-compose up`` command.
 
 
-If you'll ever want to destroy Docker setup because you no longer need it, run this command::
+After development server starts, visit the ``http://127.0.0.1:8000/`` in your browser to see your Misago installation.
 
 
-    docker-compose down
+Admin Control Panel is available under the ``http://127.0.0.1:8000/admincp/`` address. To log in to it use ``Admin`` username and ``password`` password.
 
 
+The ``./dev`` utility implements other features besides the ``init``. Run it without any arguments to get the list of available actions.
+ 
 
 
 Frontend
 Frontend
 --------
 --------
@@ -160,8 +146,8 @@ English sentences used within ``misago.faker.phrases`` were extracted from `Nati
 Copyright and license
 Copyright and license
 =====================
 =====================
 
 
-**Misago** - Copyright © 2016 `Rafał Pitoń <http://github.com/ralfp>`_
+**Misago** - Copyright © 2018 `Rafał Pitoń <http://github.com/ralfp>`_
 This program comes with ABSOLUTELY NO WARRANTY.
 This program comes with ABSOLUTELY NO WARRANTY.
 
 
 This is free software and you are welcome to modify and redistribute it under the conditions described in the license.
 This is free software and you are welcome to modify and redistribute it under the conditions described in the license.
-For the complete license, refer to LICENSE.rst
+For the complete license, refer to LICENSE.rst

+ 0 - 6
cleansource

@@ -1,6 +0,0 @@
-#!/bin/bash
-
-yapf -ir ${1:-misago} -e '*/project_template/**/*.py' -e '*/conf/defaults.py'
-isort -rc ${1:-misago}
-pylint ${1:-misago}
-python pycodestyle.py ${1:-misago}

+ 375 - 0
dev

@@ -0,0 +1,375 @@
+#!/bin/bash
+# devctl is an utility script for automating some development tasks and actions.
+# To find out what options are available, run it without any arguments.
+
+# Text styles
+RED='\033[0;31m'
+BOLD=$(tput bold)
+NORMAL=$(tput sgr0)
+
+# Define dev paths
+# Those are paths to dirs and files created for dev project
+dev_paths=(
+    "./avatargallery"
+    "./devproject"
+    "./media"
+    "./static"
+    "./theme"
+    "./userdata"
+    "./cron.txt"
+    "./manage.py"
+)
+
+# Required ports
+# Some tasks test for those ports before continuing
+port_django=8000
+port_postgresql=5432
+
+required_ports=($port_postgresql $port_django)
+
+# Default superuser
+username="Admin"
+password="password"
+email="admin@example.com"
+
+# Utility functions used by action commands
+error() {
+    echo -e "${RED}Error:${NORMAL} $1"
+}
+
+require_in_docker() {
+    if [[ ! $IN_MISAGO_DOCKER = 1 ]]; then
+        error "This command can only be ran inside the running Misago docker container."
+        exit 1
+    fi
+}
+
+wait_for_db() {
+    require_in_docker
+
+    export PGPASSWORD=$POSTGRES_PASSWORD
+    RETRIES=10
+
+    until psql -h $POSTGRES_HOST -U $POSTGRES_USER -d $POSTGRES_DB -c "select 1" > /dev/null 2>&1 || [ $RETRIES -eq 0 ]; do
+        echo "Waiting for PostgreSQL to start, $((RETRIES--)) remaining attempts..."
+        sleep 2
+    done
+}
+
+# Check if user has docker-compose
+if [[ ! $IN_MISAGO_DOCKER = 1 ]]; then
+    if ! command -v docker-compose >/dev/null 2>&1; then
+        error "You need to have Docker installed to use this tool."
+        echo
+        echo "Docker release for your system can be downloaded for free from this page:"
+        echo "https://www.docker.com/get-started"
+        echo
+        exit 1
+    fi
+fi
+
+# Commands
+intro() {
+    echo "Usage: ./dev [arg] ..."
+    echo "Arguments grouped by type:"
+    echo
+    echo "Development project:"
+    echo
+    echo "    ${BOLD}init${NORMAL}              initialize new dev project for development, does nothing if project already exists."
+    echo "    ${BOLD}afterinit${NORMAL}         repeat help message displayed after init command is complete."
+    echo "    ${BOLD}remove${NORMAL}            if dev project exists, remove its files and docker containers."
+    echo "    ${BOLD}rebuild${NORMAL}           rebuild docker containers."
+    echo "    ${BOLD}reset${NORMAL}             run remove followed by init."
+    echo
+    echo "    Both init and rebuild args can be followed with any number of extra args and options that should be appended to docker-compose build."
+    echo
+    echo "Testing:"
+    echo
+    echo "    ${BOLD}test${NORMAL}              run tests suite."
+    echo "    ${BOLD}test module${NORMAL}       run tests suite in specified python module, eg. misago.users."
+    echo
+    echo "Translations:"
+    echo
+    echo "    ${BOLD}makemessages${NORMAL}      update translation files for \"en\" language."
+    echo "    ${BOLD}makemessages lang${NORMAL} update translation files for \"lang\" language."
+    echo "    ${BOLD}compilemessages${NORMAL}   compile translation files to \"mo\" format."
+    echo "    ${BOLD}txpull${NORMAL}            pull translations from Transifex."
+    echo "    ${BOLD}txpush${NORMAL}            push new source files to Transifex."
+    echo "    ${BOLD}txsync${NORMAL}            runs entire process of syncing translations with Transifex."
+    echo
+    echo "Shortcuts:"
+    echo
+    echo "    ${BOLD}manage.py${NORMAL}         runs \"python manage.py\" inside docker."
+    echo "    ${BOLD}bash${NORMAL}              starts bash session inside running Misago container."
+    echo "    ${BOLD}run${NORMAL}               runs \"docker-compose run --rm misago\"."
+    echo "    ${BOLD}psql${NORMAL}              runs psql connected to development database."
+    echo
+}
+
+# Handle invalid argument
+invalid_argument() {
+    echo -e "Invalid argument: ${RED}$1${NORMAL}"
+    echo "Please run this script without any arguments to see the list of available arguments."
+    exit 1
+}
+
+# Initialize new dev project
+init() {
+    for dev_path in "${dev_paths[@]}"; do
+        if [ -e $dev_path ]; then
+            error "Dev project already exists, or was not deleted completely."
+            echo
+            echo "Please use \"remove\" option to remove any possible remaining files and try again."
+            echo
+            exit 1
+        fi
+    done
+
+    for port in "${required_ports[@]}"; do
+        nc "127.0.0.1" "$port" < /dev/null
+        if [[ $? = "0" ]]; then
+            if [[ $port = $port_django ]]; then
+                error "Other application appears to already be running on http://127.0.0.1:8000"
+            elif [[ $port = $port_postgresql ]]; then
+                error "PostgreSQL appears to already be running on the port $port."
+                echo
+                echo "Misago runs its own PostgreSQL instance in the docker container and uses port $port to expose it to other programs."
+                echo "Please stop your PostgreSQL server and try again."
+                echo
+            fi
+            exit 1
+        fi
+    done
+    
+    docker-compose stop
+    docker-compose build --pull --force-rm "${@:2}"
+    docker-compose run --rm misago ./dev init_in_docker
+}
+
+# Initialization step that has to occur inside docker
+init_in_docker() {
+    require_in_docker
+    wait_for_db
+    # initialize django project
+    python misago/bin/misago-start-devproject.py
+    # move items of interest up one level
+    mv devproject/devproject devproject_tmp
+    mv devproject/avatargallery ./avatargallery
+    mv devproject/media ./media
+    mv devproject/userdata ./userdata
+    mv devproject/manage.py ./manage.py
+    rm -rf devproject
+    mv devproject_tmp devproject
+    # migrate the database
+    python manage.py migrate
+    # create superuser Admin with password "password"
+    python manage.py createsuperuser --username $username --email $email --password $password
+
+    # display after init message
+    echo
+    echo "================================================================================"
+    after_init_message
+}
+
+# After-init message
+after_init_message() {
+    echo
+    echo "You can now start the development server using:"
+    echo
+    echo "    docker-compose up"
+    echo
+    echo "Running server will be available in the browser under the http://127.0.0.1:8000 address."
+    echo
+    echo "Default superuser has been created with this username and password:"
+    echo
+    echo "Username:    $username"
+    echo "Password:    $password"
+    echo
+    echo "Development project directories:"
+    echo
+    echo "devproject        configuration files for development instance."
+    echo "media             user uploaded files."
+    echo "userdata          working directory for user data exports."
+    echo "avatargallery     example avatar gallery."
+    echo
+    echo "To connect to development database use following credentials:"
+    echo
+    echo "User:         misago"
+    echo "Password:     misago"
+    echo "Database:     misago"
+    echo "Host:         postgres"
+    echo "Port:         5432"
+    echo
+    echo "Note: development server must be running for connection to be possible."
+    echo
+}
+
+# Remove existing dev project
+remove() {
+    echo -e "${RED}Warning:${NORMAL} You are going remove current development project."
+    echo
+
+    will_delete_files=false
+    for dev_path in "${dev_paths[@]}"; do
+        if [ -e $dev_path ]; then
+            will_delete_files=true
+        fi
+    done
+    if [[ $will_delete_files = true ]]; then
+        echo "Following files and directories will be deleted:"
+        for dev_path in "${dev_paths[@]}"; do
+            if [ -e $dev_path ]; then
+                echo "  $dev_path"
+            fi
+        done
+        echo
+    fi
+
+    echo "Enter \"y\" to confirm:"
+
+    read confirmation
+    if [[ $confirmation = "y" ]]; then
+        for dev_path in "${dev_paths[@]}"; do
+            if [ -e $dev_path ]; then
+                echo "Removing $dev_path"
+                rm -rf $dev_path
+            fi
+        done
+
+        docker-compose stop
+        docker-compose down --remove-orphans
+    else
+        echo "Operation canceled."
+    fi
+}
+
+# Rebuild docker containers
+rebuild() {
+    docker-compose stop
+    docker-compose build --pull --force-rm "${@:2}"
+}
+
+# Run tests suite
+test() {
+    docker-compose run --rm misago runtests.py $1
+    docker-compose stop
+}
+
+# Make messages
+makemessages() {
+    docker-compose run --rm --no-deps misago ./dev makemessages_in_docker $1
+}
+
+# Docker part of makemessages
+makemessages_in_docker() {
+    require_in_docker
+    
+    echo "Extracting messages for $1 language:"
+    cd ./misago
+
+    echo "Processing .py and .html files..."
+    django-admin.py makemessages -l $1 -e html,txt,py > /dev/null
+
+    echo "Processing .js files..."
+    django-admin.py makemessages -l $1 -d djangojs > /dev/null
+}
+
+# Compile messages
+compilemessages() {
+    docker-compose run --rm --no-deps misago ./dev compilemessages_in_docker
+}
+
+# Docker part of compile messages
+compilemessages_in_docker() {
+    require_in_docker
+    cd ./misago
+    django-admin.py compilemessages
+}
+
+# Pull translation files from transifex
+txpull() {
+    tx pull
+    compilemessages
+}
+
+# Push translation sources to transifex
+txpush() {
+    tx push --source
+}
+
+# Shortcut for starting bash session in running container
+run_bash() {
+    docker exec -it misago_misago_1 bash
+}
+
+# Shortcut for docker-compose run --rm misago python manage.py
+run_managepy() {
+    docker-compose run --rm misago python manage.py "${@:2}"
+}
+
+# Shortcut for docker-compose run --rm misago...
+docker_run() {
+    docker-compose run --rm misago "${@:2}"
+}
+
+# Shortcut for psql
+run_psql() {
+    docker-compose run --rm misago ./dev psql_in_docker
+}
+
+# Docker part of psql shortcut
+psql_in_docker() {
+    wait_for_db
+    PGPASSWORD=$POSTGRES_PASSWORD psql --username $POSTGRES_USER --host $POSTGRES_HOST $POSTGRES_DB
+}
+
+# Command dispatcher
+if [[ $1 ]]; then
+    if [[ $1 = "init" ]]; then
+        init $@
+    elif [[ $1 = "init_in_docker" ]]; then
+        init_in_docker
+    elif [[ $1 = "afterinit" ]]; then
+        after_init_message
+    elif [[ $1 = "remove" ]]; then
+        remove
+    elif [[ $1 = "reset" ]]; then
+        remove
+        init $@
+    elif [[ $1 = "rebuild" ]]; then
+        rebuild $@
+    elif [[ $1 = "test" ]]; then
+        test $2
+    elif [[ $1 = "makemessages" ]]; then
+        makemessages ${2:-en}
+    elif [[ $1 = "makemessages_in_docker" ]]; then
+        makemessages_in_docker $2
+    elif [[ $1 = "compilemessages" ]]; then
+        compilemessages
+    elif [[ $1 = "compilemessages_in_docker" ]]; then
+        compilemessages_in_docker
+    elif [[ $1 = "txpull" ]]; then
+        txpull
+    elif [[ $1 = "txpush" ]]; then
+        txpush
+    elif [[ $1 = "txsync" ]]; then
+        makemessages en
+        txpush
+        txpull
+        compilemessages
+    elif [[ $1 = "bash" ]]; then
+        run_bash
+    elif [[ $1 = "manage.py" ]]; then
+        run_managepy $@
+    elif [[ $1 = "run" ]]; then
+        docker_run $@
+    elif [[ $1 = "psql" ]]; then
+        run_psql
+    elif [[ $1 = "psql_in_docker" ]]; then
+        psql_in_docker
+    else
+        invalid_argument $1
+    fi
+else
+    intro
+fi

+ 0 - 2
docker-compose.yaml

@@ -13,8 +13,6 @@ services:
     build: .
     build: .
     command: python manage.py runserver 0.0.0.0:8000
     command: python manage.py runserver 0.0.0.0:8000
     environment:
     environment:
-      # Name of development project
-      - PROJECT_NAME=devproject
       # Postgres
       # Postgres
       - POSTGRES_USER=misago
       - POSTGRES_USER=misago
       - POSTGRES_PASSWORD=misago
       - POSTGRES_PASSWORD=misago

+ 0 - 0
extras/__init__.py


+ 0 - 5
extras/config.py

@@ -1,5 +0,0 @@
-from django.utils.six.moves import configparser
-
-
-yapf = configparser.ConfigParser()
-yapf.read('.style.yapf')

+ 0 - 27
extras/createsuperuser.py

@@ -1,27 +0,0 @@
-"""
-Create superuser for the devproject
-"""
-
-import os
-import django
-
-os.environ['DJANGO_SETTINGS_MODULE'] = '{}.settings'.format(os.environ['PROJECT_NAME'])
-django.setup()
-
-from django.contrib.auth import get_user_model
-from django.utils.crypto import get_random_string
-
-User = get_user_model()
-
-
-if User.objects.count() == 0:
-    superuser = User.objects.create_superuser(
-        os.environ['SUPERUSER_USERNAME'],
-        os.environ['SUPERUSER_EMAIL'],
-        get_random_string(10), # set throwaway password
-        set_default_avatar=True,
-    )
-
-    # Override user's throwaway password with configured one
-    superuser.set_password(os.environ['SUPERUSER_PASSWORD'])
-    superuser.save()

+ 0 - 93
extras/fixabsoluteimports.py

@@ -1,93 +0,0 @@
-import os
-
-
-def walk_directory(root, dirs, files):
-    for file_path in files:
-        if 'project_template' not in root and file_path.lower().endswith('.py'):
-            clean_file(os.path.join(root, file_path))
-
-
-def clean_file(file_path):
-    py_source = file(file_path).read()
-    if 'misago.' in py_source:
-        package = file_path.rstrip('.py').split('/')
-
-        parse_file = True
-        save_file = False
-        cursor = 0
-
-        while parse_file:
-            try:
-                import_start = py_source.index('from ', cursor)
-                import_end = py_source.index(' import', import_start)
-                cursor = import_end
-
-                import_len = import_end - import_start
-                import_path = py_source[import_start:import_end].lstrip('from').strip()
-
-                if import_path.startswith('misago.'):
-                    cleaned_import = clean_import(package, import_path.split('.'))
-                    if cleaned_import:
-                        save_file = True
-
-                        cleaned_import_string = 'from {}'.format('.'.join(cleaned_import))
-                        py_source = ''.join((py_source[:import_start], cleaned_import_string, py_source[import_end:]))
-                        cursor -= import_end - import_start - len(cleaned_import_string)
-            except ValueError:
-                parse_file = False
-
-        if save_file:
-            with open(file_path, 'w') as package:
-                print file_path
-                package.write(py_source)
-
-
-def clean_import(package, import_path):
-    if len(package) < 2 or len(import_path) < 2:
-        # skip non-app import
-        return
-
-    if package[:2] != import_path[:2]:
-        # skip other app import
-        return
-
-    if package == import_path:
-        # import from direct child
-        return ['', '']
-
-    if package[:-1] == import_path[:-1]:
-        # import from sibling module
-        return ['', import_path[-1]]
-
-    if len(package) < len(import_path) and package == import_path[:len(package)]:
-        # import from child module
-        return [''] + import_path[len(package):]
-
-    if len(package) > len(import_path) and package[:len(import_path)] == import_path:
-        # import from parent module
-        return [''] * (len(package) - len(import_path) + 1)
-
-    # relative up and down path
-    relative_path = []
-
-    # find upwards path
-    overlap_len = 2
-    while True:
-        if package[:overlap_len + 1] != import_path[:overlap_len + 1]:
-            break
-        overlap_len += 1
-
-    relative_path += ([''] * (len(package) - overlap_len))[:-1]
-
-    # append eventual downwards path
-    if import_path[overlap_len:]:
-        relative_path += [''] + import_path[overlap_len:]
-
-    return relative_path
-
-
-if __name__ == '__main__':
-    for args in os.walk('../misago'):
-        walk_directory(*args)
-
-    print "\nDone! Don't forget to run isort to fix imports ordering!"

+ 0 - 160
extras/fixdictsformatting.py

@@ -1,160 +0,0 @@
-from __future__ import unicode_literals
-
-import sys
-
-from lib2to3.pytree import Node, Leaf
-from lib2to3.fixer_util import token, syms
-
-from yapf.yapflib import pytree_utils
-
-from django.utils import six
-
-from .config import yapf as yapf_config
-
-
-MAX_LINE_LENGTH = yapf_config.getint('style', 'column_limit') + 1
-
-
-def fix_formatting(filesource):
-    if not ('{'  in filesource and ('[' in filesource or '(' in filesource)):
-        return filesource
-
-    tree = pytree_utils.ParseCodeToTree(filesource)
-    for node in tree.children:
-        walk_tree(node, node.children)
-    return six.text_type(tree)
-
-
-def walk_tree(node, children):
-    for item in children:
-        if item.type == syms.dictsetmaker:
-            indent = item.parent.children[-1].column
-            walk_dict_tree(item, item.children, indent)
-        else:
-            walk_tree(item, item.children)
-
-
-def walk_dict_tree(node, children, indent):
-    for item in children:
-        prev = item.prev_sibling
-        if isinstance(prev, Leaf) and prev.value == ':':
-            if isinstance(item, Leaf):
-                if six.text_type(item).startswith("\n"):
-                    item.replace(Leaf(
-                        item.type,
-                        item.value,
-                        prefix=' ',
-                    ))
-            elif six.text_type(item).strip()[0] in ('[', '{'):
-                walk_tree(item, item.children)
-            else:
-                walk_dedent_tree(item, item.children, indent)
-
-
-def walk_dedent_tree(node, children, indent):
-    force_split_next = False
-    for item in children:
-        prev = item.prev_sibling
-        if not prev:
-            if isinstance(item, Leaf) and six.text_type(item).startswith("\n"):
-                prev = node.prev_sibling
-                next = node.next_sibling
-                final_length = 0
-
-                if prev and "\n" not in six.text_type(node).strip():
-                    final_length = prev.column + len(six.text_type(node).strip()) + 3
-
-                item.replace(Leaf(
-                    item.type,
-                    item.value,
-                    prefix=' ',
-                ))
-
-                if final_length and final_length > MAX_LINE_LENGTH:
-                    # tell next call to walk_dedent_tree_node that we need
-                    # different stringformat tactic
-                    force_split_next = True
-        elif isinstance(item, Node):
-            if node.type == syms.power:
-                for subitem in item.children[1:]:
-                    walk_dedent_power_node(subitem, subitem.children, indent)
-            else:
-                for subitem in item.children[1:]:
-                    walk_dedent_tree_node(subitem, subitem.children, indent, force_split_next)
-                    force_split_next = False
-
-
-def walk_dedent_tree_node(node, children, indent, force_split_next=False):
-    if six.text_type(node).startswith("\n"):
-        if isinstance(node, Leaf):
-            prev = node.prev_sibling
-            next = node.next_sibling
-
-            is_followup = prev and prev.type == token.STRING and node.type == token.STRING
-            if is_followup:
-                new_value = node.value
-
-                # insert linebreak after last string in braces, so its closing brace moves to new line
-                if not node.next_sibling:
-                    closing_bracket = node.parent.parent.children[-1]
-                    if not six.text_type(closing_bracket).startswith("\n"):
-                        new_value = "%s\n%s" % (node.value, (' ' * (indent + 4)))
-
-                node.replace(Leaf(
-                    node.type,
-                    new_value,
-                    prefix="\n%s" % (' ' * (indent + 8)),
-                ))
-            else:
-                if six.text_type(node).strip() in (')', '}'):
-                    new_prefix = "\n%s" % (' ' * (indent + 4))
-                else:
-                    new_prefix = "\n%s" % (' ' * (indent + 8))
-
-                node.replace(Leaf(
-                    node.type,
-                    node.value,
-                    prefix=new_prefix
-                ))
-        else:
-            for item in children:
-                walk_dedent_tree_node(item, item.children, indent)
-    elif isinstance(node, Leaf):
-        if node.type == token.STRING:
-            strings_tuple = node.parent.parent
-
-            prev = node.prev_sibling
-            next = node.next_sibling
-
-            is_opening = prev is None and six.text_type(strings_tuple).strip()[0] == '('
-            has_followup = next and next.type == token.STRING
-
-            if is_opening and has_followup:
-                node.replace(Leaf(
-                    node.type,
-                    node.value,
-                    prefix="\n%s" % (' ' * (indent + 8)),
-                ))
-            elif force_split_next:
-                node.replace(Leaf(
-                    node.type,
-                    "%s\n%s" % (node.value, (' ' * (indent + 4))),
-                    prefix="\n%s" % (' ' * (indent + 8)),
-                ))
-    else:
-        for item in children:
-            walk_dedent_tree_node(item, item.children, indent)
-
-
-def walk_dedent_power_node(node, children, indent):
-    if isinstance(node, Leaf):
-        if six.text_type(node).startswith("\n"):
-            node.replace(Leaf(
-                node.type,
-                node.value,
-                prefix=node.prefix[:-4],
-            ))
-    else:
-        for item in children:
-            walk_dedent_power_node(item, item.children, indent)
-

+ 0 - 53
extras/fixrelativeimports.py

@@ -1,53 +0,0 @@
-import os
-import re
-
-
-RELATIVE_IMPORT = re.compile(r'(from|import) \.\.+([a-z]+)?')
-
-
-def walk_directory(root, dirs, files):
-    for file_path in files:
-        if 'project_template' not in root and file_path.lower().endswith('.py'):
-            clean_file(os.path.join(root, file_path))
-
-
-def clean_file(file_path):
-    py_source = file(file_path).read()
-    if 'from ..' in py_source or 'import ..' in py_source:
-        print '====' * 8
-        print file_path
-        print '====' * 8
-
-        package = file_path.rstrip('.py').split('/')
-
-        def replace_import(matchobj):
-            prefix, suffix = matchobj.group(0).split()
-            return '{} {}'.format(prefix, clean_import(package, suffix))
-
-        py_source = RELATIVE_IMPORT.sub(replace_import, py_source)
-
-        #print py_source
-        with open(file_path, 'w') as package:
-            print file_path
-            package.write(py_source)
-
-
-def clean_import(package, match):
-    path = match[1:]
-
-    import_path = package[:]
-    while match and match[0] == '.':
-        import_path = import_path[:-1]
-        match = match[1:]
-
-    if match:
-        import_path.append(match)
-
-    return '.'.join(import_path)
-
-
-if __name__ == '__main__':
-    for args in os.walk('../misago'):
-        walk_directory(*args)
-
-    print "\nDone! Don't forget to run isort to fix imports ordering!"

+ 0 - 2
extras/psql.sh

@@ -1,2 +0,0 @@
-#!/usr/bin/env bash
-PGPASSWORD=$POSTGRES_PASSWORD psql --username $POSTGRES_USER --host $POSTGRES_HOST $POSTGRES_DB

+ 0 - 10
extras/wait_for_postgres.sh

@@ -1,10 +0,0 @@
-#!/usr/bin/env bash
-# Sometimes postgres is not ready before django attempts to connect.
-# This script waits until we can do a basic select before continuing.
-export PGPASSWORD=$POSTGRES_PASSWORD
-RETRIES=10
-
-until psql -h $POSTGRES_HOST -U $POSTGRES_USER -d $POSTGRES_DB -c "select 1" > /dev/null 2>&1 || [ $RETRIES -eq 0 ]; do
-  echo "Waiting for postgres server, $((RETRIES--)) remaining attempts..."
-  sleep 5
-done

+ 0 - 58
initdev

@@ -1,58 +0,0 @@
-#!/usr/bin/env bash
-# This script is a shortcut for configuring newly-build docker container for Misago development
-python setup.py develop
-
-# Clear OS files
-rm -f /srv/misago/.DS_Store
-rm -f /srv/misago/Thumbs.db
-
-# If user specified "-f", clear after previous devinit
-if [ "$1" = "-f" ]
-then
-    echo "Cleaned files created by previous initdev"
-    rm -f /srv/misago/cron.txt
-    rm -f /srv/misago/manage.py
-    rm -rf /srv/misago/avatargallery
-    rm -rf /srv/misago/$PROJECT_NAME
-    rm -rf /srv/misago/media
-    rm -rf /srv/misago/static
-    rm -rf /srv/misago/theme
-    rm -rf /srv/misago/userdata
-fi
-
-# Create new project
-python extras/createdevproject.py $PROJECT_NAME /srv/misago
-
-# Clean up unnecessary project files
-rm -rf theme
-rm -f cron.txt
-
-# Database
-./extras/wait_for_postgres.sh
-python manage.py migrate
-python extras/createsuperuser.py
-
-# Print short bit of help at the end of cript
-RED='\033[0;31m'
-DEFAULT='\033[0m'
-
-echo ""
-echo "================================================================================"
-echo ""
-echo "Note: running 'initdev' after already having used it to setup Misago"
-echo "for development may result in any of following errors occuring:"
-echo ""
-echo "  - CommandError: /srv/misago/... already exists, overlaying a project or app into an existing directory won't replace conflicting files"
-echo "  - ModuleNotFoundError: No module named '$PROJECT_NAME'"
-echo "  - django.core.exceptions.ImproperlyConfigured: settings.DATABASES is improperly configured. Please supply the NAME value."
-echo "  - python: can't open file 'manage.py': [Errno 2] No such file or directory"
-echo ""
-echo "If you are experiencing either of those errors, this means that files are"
-echo "present in the repository's main directory preventing 'initdev' from succedding."
-echo "Please try running the 'initdev' with \"-f\" option to force old files deletion:"
-echo ""
-echo "  docker-compose run --rm misago initdev -f"
-echo ""
-echo -e "${RED}Warning:${DEFAULT} if you have uncommited changes to Misago's setup that should be included"
-echo "in next release, make sure that they are commited to 'misago/project_template'"
-echo "or 'initdev -f' will overwrite the files causing them to be lost."

+ 0 - 8
makemessages

@@ -1,8 +0,0 @@
-#!/bin/bash
-cd misago
-
-echo "Extracting messages from .py and .html files..."
-django-admin.py makemessages -l $1 -e html,txt,py
-
-echo "Extracting messages from js files..."
-django-admin.py makemessages -l $1 -d djangojs

+ 1 - 1
misago/__init__.py

@@ -1 +1 @@
-__version__ = '0.19.1'
+__version__ = '0.19.2'

+ 11 - 24
misago/admin/views/index.py

@@ -1,12 +1,6 @@
 import requests
 import requests
 from requests.exceptions import RequestException
 from requests.exceptions import RequestException
 
 
-try:
-    from packaging.version import parse as parse_version
-    ALLOW_VERSION_CHECK = True
-except ImportError:
-    ALLOW_VERSION_CHECK = False
-
 from django.contrib.auth import get_user_model
 from django.contrib.auth import get_user_model
 from django.http import Http404, JsonResponse
 from django.http import Http404, JsonResponse
 from django.utils.translation import ugettext as _
 from django.utils.translation import ugettext as _
@@ -40,8 +34,6 @@ def admin_index(request):
         request, 'misago/admin/index.html', {
         request, 'misago/admin/index.html', {
             'db_stats': db_stats,
             'db_stats': db_stats,
             'address_check': check_misago_address(request),
             'address_check': check_misago_address(request),
-
-            'allow_version_check': ALLOW_VERSION_CHECK,
             'version_check': cache.get(VERSION_CHECK_CACHE_KEY),
             'version_check': cache.get(VERSION_CHECK_CACHE_KEY),
         }
         }
     )
     )
@@ -59,36 +51,31 @@ def check_misago_address(request):
 
 
 
 
 def check_version(request):
 def check_version(request):
-    if not ALLOW_VERSION_CHECK or request.method != "POST":
+    if request.method != "POST":
         raise Http404()
         raise Http404()
 
 
     version = cache.get(VERSION_CHECK_CACHE_KEY, 'nada')
     version = cache.get(VERSION_CHECK_CACHE_KEY, 'nada')
 
 
     if version == 'nada':
     if version == 'nada':
         try:
         try:
-            api_url = 'https://api.github.com/repos/rafalp/Misago/releases'
+            api_url = 'https://pypi.org/pypi/Misago/json'
             r = requests.get(api_url)
             r = requests.get(api_url)
+            r.raise_for_status()
 
 
-            if r.status_code != requests.codes.ok:
-                r.raise_for_status()
-
-            latest_version = r.json()[0]['tag_name']
-
-            latest = parse_version(latest_version)
-            current = parse_version(__version__)
+            latest_version = r.json()['info']['version']
 
 
-            if latest > current:
+            if latest_version == __version__:
                 version = {
                 version = {
-                    'is_error': True,
-                    'message': _("Outdated: %(current)s! (latest: %(latest)s)") % {
-                        'latest': latest_version,
+                    'is_error': False,
+                    'message': _("Up to date! (%(current)s)") % {
                         'current': __version__,
                         'current': __version__,
                     },
                     },
                 }
                 }
             else:
             else:
                 version = {
                 version = {
-                    'is_error': False,
-                    'message': _("Up to date! (%(current)s)") % {
+                    'is_error': True,
+                    'message': _("Outdated: %(current)s! (latest: %(latest)s)") % {
+                        'latest': latest_version,
                         'current': __version__,
                         'current': __version__,
                     },
                     },
                 }
                 }
@@ -97,7 +84,7 @@ def check_version(request):
         except (RequestException, IndexError, KeyError, ValueError) as e:
         except (RequestException, IndexError, KeyError, ValueError) as e:
             version = {
             version = {
                 'is_error': True,
                 'is_error': True,
-                'message': _("Failed to connect to GitHub API. Try again later."),
+                'message': _("Failed to connect to pypi.org API. Try again later."),
             }
             }
 
 
     return JsonResponse(version)
     return JsonResponse(version)

+ 26 - 16
extras/createdevproject.py → misago/bin/misago-start-devproject.py

@@ -1,3 +1,5 @@
+#!/usr/bin/env python
+# pylint: disable=E0401
 """
 """
 Creates a dev project for local development
 Creates a dev project for local development
 """
 """
@@ -5,27 +7,20 @@ Creates a dev project for local development
 import os
 import os
 import sys
 import sys
 
 
-from misago.core import setup
+BASE_DIR = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
 
 
-
-BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
+PROJECT_NAME = 'devproject'
 
 
 
 
 def main():
 def main():
-    project_name = os.environ['PROJECT_NAME']
-
-    # Allow for overriding project name
-    if len(sys.argv) > 1:
-        project_name = sys.argv[1]
-    else:
-        sys.argv.append(project_name)
-
-    settings_file = os.path.join(BASE_DIR, project_name, 'settings.py')
+    settings_file = os.path.join(BASE_DIR, PROJECT_NAME, PROJECT_NAME, 'settings.py')
 
 
     # Avoid recreating if already present
     # Avoid recreating if already present
     if os.path.exists(settings_file):
     if os.path.exists(settings_file):
         return
         return
 
 
+    from misago.core import setup
+
     setup.start_misago_project()
     setup.start_misago_project()
     fill_in_settings(settings_file)
     fill_in_settings(settings_file)
 
 
@@ -34,20 +29,33 @@ def fill_in_settings(f):
     with open(f, 'r') as fd:
     with open(f, 'r') as fd:
         s = fd.read()
         s = fd.read()
 
 
-        # Postgres
+        # Read PostgreSQL's config from env variables
         s = s.replace("'NAME': '',", "'NAME': os.environ.get('POSTGRES_DB'),")
         s = s.replace("'NAME': '',", "'NAME': os.environ.get('POSTGRES_DB'),")
         s = s.replace("'USER': '',", "'USER': os.environ.get('POSTGRES_USER'),")
         s = s.replace("'USER': '',", "'USER': os.environ.get('POSTGRES_USER'),")
         s = s.replace("'PASSWORD': '',", "'PASSWORD': os.environ.get('POSTGRES_PASSWORD'),")
         s = s.replace("'PASSWORD': '',", "'PASSWORD': os.environ.get('POSTGRES_PASSWORD'),")
         s = s.replace("'HOST': 'localhost',", "'HOST': os.environ.get('POSTGRES_HOST'),")
         s = s.replace("'HOST': 'localhost',", "'HOST': os.environ.get('POSTGRES_HOST'),")
 
 
         # Specify console backend for email
         # Specify console backend for email
-        s += "\nEMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'\n"
+        s += "\n"
+        s += "\n# Set dev instance to send e-mails to console"
+        s += "\n"
+        s += "\nEMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'"
+        s += "\n"
+
+        # Tie Debug Toolbar visibility to env variable
+        s += "\n"
+        s += "\n# Display debug toolbar if IN_MISAGO_DOCKER enviroment var is set to \"1\""
+        s += "\n"
+        s += "\nDEBUG_TOOLBAR_CONFIG = {"
+        s += "\n    'SHOW_TOOLBAR_CALLBACK': 'misago.conf.debugtoolbar.enable_debug_toolbar'"
+        s += "\n}"
+        s += "\n"
 
 
         # Empty the contents of STATICFILES_DIRS (STATICFILES_DIRS = [])
         # Empty the contents of STATICFILES_DIRS (STATICFILES_DIRS = [])
         pos = s.find('STATICFILES_DIRS')
         pos = s.find('STATICFILES_DIRS')
         s = s[:s.find('[', pos) + 1] + s[s.find(']', pos):]
         s = s[:s.find('[', pos) + 1] + s[s.find(']', pos):]
 
 
-        # Remote theme dir from template dirs
+        # Remove theme dir from template dirs
         pos = s.find("'DIRS': [")
         pos = s.find("'DIRS': [")
         s = s[:s.find('[', pos) + 1] + s[s.find(']', pos):]
         s = s[:s.find('[', pos) + 1] + s[s.find(']', pos):]
 
 
@@ -56,4 +64,6 @@ def fill_in_settings(f):
 
 
 
 
 if __name__ == '__main__':
 if __name__ == '__main__':
-    main()
+    sys.argv.append(PROJECT_NAME)
+    sys.path.append(BASE_DIR)
+    main()

+ 5 - 0
misago/conf/debugtoolbar.py

@@ -0,0 +1,5 @@
+import os
+
+
+def enable_debug_toolbar(_):
+    return os.environ.get('IN_MISAGO_DOCKER', '') == "1"

+ 22 - 13
misago/core/management/progressbar.py

@@ -6,18 +6,27 @@ def show_progress(command, step, total, since=None):
     filled = progress // 2
     filled = progress // 2
     blank = 50 - filled
     blank = 50 - filled
 
 
-    line = '\r%s%% [%s%s]'
-    rendered_line = line % (str(progress).rjust(3), '=' * filled, ' ' * blank)
+    template = '\r%(step)s (%(progress)s%%) [%(progressbar)s]%(estimation)s'
+    variables = {
+        "step": str(step).rjust(len(str(total))),
+        "progress": str(progress).rjust(3),
+        "progressbar": "".join(['=' * filled, ' ' * blank]),
+        "estimation": get_estimation_str(since, progress, step, total),
+    }
 
 
-    if since:
-        progress_float = float(step) * 100.0 / float(total)
-        if progress_float > 0:
-            step_time = (time.time() - since) / progress_float
-            estimated_time = (100 - progress) * step_time
-            clock = time.strftime('%H:%M:%S', time.gmtime(estimated_time))
-            rendered_line = '%s %s est.' % (rendered_line, clock)
-        else:
-            rendered_line = '%s --:--:-- est.' % rendered_line
-
-    command.stdout.write(rendered_line, ending='')
+    command.stdout.write(template % variables, ending='')
     command.stdout.flush()
     command.stdout.flush()
+
+
+def get_estimation_str(since, progress, step, total):
+    if not since:
+        return ""
+    
+    progress_float = float(step) * 100.0 / float(total)
+    if progress_float == 0:
+        return ' --:--:-- est.'
+
+    step_time = (time.time() - since) / progress_float
+    estimated_time = (100 - progress) * step_time
+    clock = time.strftime('%H:%M:%S', time.gmtime(estimated_time))
+    return ' %s est.' % clock

+ 1 - 2
misago/faker/management/commands/createfakebans.py

@@ -93,8 +93,6 @@ class Command(BaseCommand):
         message = 'Creating %s fake bans...\n'
         message = 'Creating %s fake bans...\n'
         self.stdout.write(message % fake_bans_to_create)
         self.stdout.write(message % fake_bans_to_create)
 
 
-        message = '\n\nSuccessfully created %s fake bans'
-
         created_count = 0
         created_count = 0
         show_progress(self, created_count, fake_bans_to_create)
         show_progress(self, created_count, fake_bans_to_create)
         for _ in range(fake_bans_to_create):
         for _ in range(fake_bans_to_create):
@@ -120,4 +118,5 @@ class Command(BaseCommand):
             created_count += 1
             created_count += 1
             show_progress(self, created_count, fake_bans_to_create)
             show_progress(self, created_count, fake_bans_to_create)
 
 
+        message = '\n\nSuccessfully created %s fake bans'
         self.stdout.write(message % created_count)
         self.stdout.write(message % created_count)

+ 1 - 2
misago/faker/management/commands/createfakecategories.py

@@ -44,8 +44,6 @@ class Command(BaseCommand):
         message = 'Creating %s fake categories...\n'
         message = 'Creating %s fake categories...\n'
         self.stdout.write(message % items_to_create)
         self.stdout.write(message % items_to_create)
 
 
-        message = '\n\nSuccessfully created %s fake categories in %s'
-
         created_count = 0
         created_count = 0
         start_time = time.time()
         start_time = time.time()
         show_progress(self, created_count, items_to_create)
         show_progress(self, created_count, items_to_create)
@@ -91,4 +89,5 @@ class Command(BaseCommand):
 
 
         total_time = time.time() - start_time
         total_time = time.time() - start_time
         total_humanized = time.strftime('%H:%M:%S', time.gmtime(total_time))
         total_humanized = time.strftime('%H:%M:%S', time.gmtime(total_time))
+        message = '\n\nSuccessfully created %s fake categories in %s'
         self.stdout.write(message % (created_count, total_humanized))
         self.stdout.write(message % (created_count, total_humanized))

+ 1 - 2
misago/faker/management/commands/createfakefollowers.py

@@ -19,8 +19,6 @@ class Command(BaseCommand):
         message = 'Adding fake followers to %s users...\n'
         message = 'Adding fake followers to %s users...\n'
         self.stdout.write(message % total_users)
         self.stdout.write(message % total_users)
 
 
-        message = '\nSuccessfully added %s fake followers in %s'
-
         total_followers = 0
         total_followers = 0
         processed_count = 0
         processed_count = 0
 
 
@@ -54,4 +52,5 @@ class Command(BaseCommand):
 
 
         total_time = time.time() - start_time
         total_time = time.time() - start_time
         total_humanized = time.strftime('%H:%M:%S', time.gmtime(total_time))
         total_humanized = time.strftime('%H:%M:%S', time.gmtime(total_time))
+        message = '\nSuccessfully added %s fake followers in %s'
         self.stdout.write(message % (total_followers, total_humanized))
         self.stdout.write(message % (total_followers, total_humanized))

+ 5 - 4
misago/faker/management/commands/createfakethreads.py

@@ -26,7 +26,7 @@ corpus_short = EnglishCorpus(max_length=150)
 
 
 
 
 class Command(BaseCommand):
 class Command(BaseCommand):
-    help = 'Creates random threads and posts for dev and testing purposes.'
+    help = 'Creates random threads for dev and testing purposes.'
 
 
     def add_arguments(self, parser):
     def add_arguments(self, parser):
         parser.add_argument(
         parser.add_argument(
@@ -44,9 +44,8 @@ class Command(BaseCommand):
 
 
         fake = Factory.create()
         fake = Factory.create()
 
 
-        self.stdout.write('Creating fake threads...\n')
-
-        message = '\nSuccessfully created %s fake threads in %s'
+        message = 'Creating %s fake threads...\n'
+        self.stdout.write(message % items_to_create)
 
 
         created_threads = 0
         created_threads = 0
         start_time = time.time()
         start_time = time.time()
@@ -160,6 +159,7 @@ class Command(BaseCommand):
 
 
         pinned_threads = random.randint(0, int(created_threads * 0.025)) or 1
         pinned_threads = random.randint(0, int(created_threads * 0.025)) or 1
         self.stdout.write('\nPinning %s threads...' % pinned_threads)
         self.stdout.write('\nPinning %s threads...' % pinned_threads)
+        
         for _ in range(0, pinned_threads):
         for _ in range(0, pinned_threads):
             thread = Thread.objects.order_by('?')[:1][0]
             thread = Thread.objects.order_by('?')[:1][0]
             if random.randint(0, 100) > 75:
             if random.randint(0, 100) > 75:
@@ -174,6 +174,7 @@ class Command(BaseCommand):
 
 
         total_time = time.time() - start_time
         total_time = time.time() - start_time
         total_humanized = time.strftime('%H:%M:%S', time.gmtime(total_time))
         total_humanized = time.strftime('%H:%M:%S', time.gmtime(total_time))
+        message = '\nSuccessfully created %s fake threads in %s'
         self.stdout.write(message % (created_threads, total_humanized))
         self.stdout.write(message % (created_threads, total_humanized))
 
 
     def fake_post_content(self):
     def fake_post_content(self):

+ 9 - 3
misago/faker/management/commands/createfakeusers.py

@@ -38,16 +38,21 @@ class Command(BaseCommand):
         message = 'Creating %s fake user accounts...\n'
         message = 'Creating %s fake user accounts...\n'
         self.stdout.write(message % items_to_create)
         self.stdout.write(message % items_to_create)
 
 
-        message = '\n\nSuccessfully created %s fake user accounts in %s'
-
         created_count = 0
         created_count = 0
         start_time = time.time()
         start_time = time.time()
         show_progress(self, created_count, items_to_create)
         show_progress(self, created_count, items_to_create)
 
 
         while created_count < items_to_create:
         while created_count < items_to_create:
             try:
             try:
-                user = UserModel.objects.create_user(
+                possible_usernames = [
                     fake.first_name(),
                     fake.first_name(),
+                    fake.last_name(),
+                    fake.name().replace(' ', ''),
+                    fake.user_name(),
+                ]
+
+                user = UserModel.objects.create_user(
+                    random.choice(possible_usernames),
                     fake.email(),
                     fake.email(),
                     'pass123',
                     'pass123',
                     set_default_avatar=False,
                     set_default_avatar=False,
@@ -64,4 +69,5 @@ class Command(BaseCommand):
 
 
         total_time = time.time() - start_time
         total_time = time.time() - start_time
         total_humanized = time.strftime('%H:%M:%S', time.gmtime(total_time))
         total_humanized = time.strftime('%H:%M:%S', time.gmtime(total_time))
+        message = '\n\nSuccessfully created %s fake user accounts in %s'
         self.stdout.write(message % (created_count, total_humanized))
         self.stdout.write(message % (created_count, total_humanized))

BIN
misago/locale/en/LC_MESSAGES/django.mo


+ 27 - 31
misago/locale/en/LC_MESSAGES/django.po

@@ -8,7 +8,7 @@ msgid ""
 msgstr ""
 msgstr ""
 "Project-Id-Version: PACKAGE VERSION\n"
 "Project-Id-Version: PACKAGE VERSION\n"
 "Report-Msgid-Bugs-To: \n"
 "Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2018-08-19 15:33+0000\n"
+"POT-Creation-Date: 2018-09-15 23:22+0000\n"
 "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
 "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
 "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
 "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
 "Language-Team: LANGUAGE <LL@li.org>\n"
 "Language-Team: LANGUAGE <LL@li.org>\n"
@@ -142,18 +142,18 @@ msgstr ""
 msgid "Action is not allowed."
 msgid "Action is not allowed."
 msgstr ""
 msgstr ""
 
 
-#: admin/views/index.py:83
+#: admin/views/index.py:70
 #, python-format
 #, python-format
-msgid "Outdated: %(current)s! (latest: %(latest)s)"
+msgid "Up to date! (%(current)s)"
 msgstr ""
 msgstr ""
 
 
-#: admin/views/index.py:91
+#: admin/views/index.py:77
 #, python-format
 #, python-format
-msgid "Up to date! (%(current)s)"
+msgid "Outdated: %(current)s! (latest: %(latest)s)"
 msgstr ""
 msgstr ""
 
 
-#: admin/views/index.py:100
-msgid "Failed to connect to GitHub API. Try again later."
+#: admin/views/index.py:87
+msgid "Failed to connect to pypi.org API. Try again later."
 msgstr ""
 msgstr ""
 
 
 #: categories/admin.py:47 templates/misago/categories/base.html:7
 #: categories/admin.py:47 templates/misago/categories/base.html:7
@@ -200,7 +200,7 @@ msgstr ""
 msgid "Only members with valid permissions can post in closed categories."
 msgid "Only members with valid permissions can post in closed categories."
 msgstr ""
 msgstr ""
 
 
-#: categories/forms.py:75 templates/misago/admin/index.html:110
+#: categories/forms.py:75 templates/misago/admin/index.html:103
 #: templates/misago/admin/users/delete.html:32 templates/misago/navbar.html:18
 #: templates/misago/admin/users/delete.html:32 templates/misago/navbar.html:18
 #: templates/misago/navbar.html:34 templates/misago/profile/threads.html:5
 #: templates/misago/navbar.html:34 templates/misago/profile/threads.html:5
 #: templates/misago/profile/threads.html:8
 #: templates/misago/profile/threads.html:8
@@ -524,7 +524,7 @@ msgstr ""
 #: threads/permissions/threads.py:164 threads/permissions/threads.py:178
 #: threads/permissions/threads.py:164 threads/permissions/threads.py:178
 #: threads/permissions/threads.py:189 threads/permissions/threads.py:205
 #: threads/permissions/threads.py:189 threads/permissions/threads.py:205
 #: threads/permissions/threads.py:216 threads/permissions/threads.py:247
 #: threads/permissions/threads.py:216 threads/permissions/threads.py:247
-#: users/forms/admin.py:605 users/models/user.py:147
+#: users/forms/admin.py:605 users/models/user.py:143
 msgid "No"
 msgid "No"
 msgstr ""
 msgstr ""
 
 
@@ -1555,23 +1555,19 @@ msgstr ""
 msgid "The settings.py is missing MISAGO_ADDRESS value."
 msgid "The settings.py is missing MISAGO_ADDRESS value."
 msgstr ""
 msgstr ""
 
 
-#: templates/misago/admin/index.html:60
+#: templates/misago/admin/index.html:59
 msgid "Misago version"
 msgid "Misago version"
 msgstr ""
 msgstr ""
 
 
-#: templates/misago/admin/index.html:83
+#: templates/misago/admin/index.html:81
 msgid "Check version"
 msgid "Check version"
 msgstr ""
 msgstr ""
 
 
-#: templates/misago/admin/index.html:89
-msgid "This feature requires \"packaging\" python module."
-msgstr ""
-
-#: templates/misago/admin/index.html:103
+#: templates/misago/admin/index.html:96
 msgid "DB Contents"
 msgid "DB Contents"
 msgstr ""
 msgstr ""
 
 
-#: templates/misago/admin/index.html:114
+#: templates/misago/admin/index.html:107
 #: templates/misago/admin/users/delete.html:36
 #: templates/misago/admin/users/delete.html:36
 #: templates/misago/admin/users/list.html:24
 #: templates/misago/admin/users/list.html:24
 #: templates/misago/profile/posts.html:5 templates/misago/profile/posts.html:8
 #: templates/misago/profile/posts.html:5 templates/misago/profile/posts.html:8
@@ -1580,7 +1576,7 @@ msgstr ""
 msgid "Posts"
 msgid "Posts"
 msgstr ""
 msgstr ""
 
 
-#: templates/misago/admin/index.html:118 templates/misago/navbar.html:40
+#: templates/misago/admin/index.html:111 templates/misago/navbar.html:40
 #: templates/misago/userslists/base.html:5
 #: templates/misago/userslists/base.html:5
 #: templates/misago/userslists/base.html:14 users/admin.py:80
 #: templates/misago/userslists/base.html:14 users/admin.py:80
 #: users/migrations/0002_users_settings.py:16
 #: users/migrations/0002_users_settings.py:16
@@ -1588,11 +1584,11 @@ msgstr ""
 msgid "Users"
 msgid "Users"
 msgstr ""
 msgstr ""
 
 
-#: templates/misago/admin/index.html:123
+#: templates/misago/admin/index.html:116
 msgid "Inactive users"
 msgid "Inactive users"
 msgstr ""
 msgstr ""
 
 
-#: templates/misago/admin/index.html:149
+#: templates/misago/admin/index.html:142
 msgid "Checking..."
 msgid "Checking..."
 msgstr ""
 msgstr ""
 
 
@@ -5992,7 +5988,7 @@ msgid "Enter each answer in new line. Answers are case-insensitive."
 msgstr ""
 msgstr ""
 
 
 #: users/migrations/0004_default_ranks.py:17
 #: users/migrations/0004_default_ranks.py:17
-#: users/migrations/0004_default_ranks.py:18 users/models/user.py:113
+#: users/migrations/0004_default_ranks.py:18 users/models/user.py:109
 msgid "Forum team"
 msgid "Forum team"
 msgstr ""
 msgstr ""
 
 
@@ -6021,43 +6017,43 @@ msgstr ""
 msgid "User must have an email address."
 msgid "User must have an email address."
 msgstr ""
 msgstr ""
 
 
-#: users/models/user.py:148
+#: users/models/user.py:144
 msgid "Notify"
 msgid "Notify"
 msgstr ""
 msgstr ""
 
 
-#: users/models/user.py:149
+#: users/models/user.py:145
 msgid "Notify with e-mail"
 msgid "Notify with e-mail"
 msgstr ""
 msgstr ""
 
 
-#: users/models/user.py:157
+#: users/models/user.py:153
 msgid "Everybody"
 msgid "Everybody"
 msgstr ""
 msgstr ""
 
 
-#: users/models/user.py:158
+#: users/models/user.py:154
 msgid "Users I follow"
 msgid "Users I follow"
 msgstr ""
 msgstr ""
 
 
-#: users/models/user.py:159
+#: users/models/user.py:155
 msgid "Nobody"
 msgid "Nobody"
 msgstr ""
 msgstr ""
 
 
-#: users/models/user.py:177
+#: users/models/user.py:173
 msgid "joined on"
 msgid "joined on"
 msgstr ""
 msgstr ""
 
 
-#: users/models/user.py:191
+#: users/models/user.py:187
 msgid "staff status"
 msgid "staff status"
 msgstr ""
 msgstr ""
 
 
-#: users/models/user.py:193
+#: users/models/user.py:189
 msgid "Designates whether the user can log into admin sites."
 msgid "Designates whether the user can log into admin sites."
 msgstr ""
 msgstr ""
 
 
-#: users/models/user.py:200
+#: users/models/user.py:196
 msgid "active"
 msgid "active"
 msgstr ""
 msgstr ""
 
 
-#: users/models/user.py:204
+#: users/models/user.py:200
 msgid ""
 msgid ""
 "Designates whether this user should be treated as active. Unselect this "
 "Designates whether this user should be treated as active. Unselect this "
 "instead of deleting accounts."
 "instead of deleting accounts."

BIN
misago/locale/en/LC_MESSAGES/djangojs.mo


+ 1 - 1
misago/locale/en/LC_MESSAGES/djangojs.po

@@ -8,7 +8,7 @@ msgid ""
 msgstr ""
 msgstr ""
 "Project-Id-Version: PACKAGE VERSION\n"
 "Project-Id-Version: PACKAGE VERSION\n"
 "Report-Msgid-Bugs-To: \n"
 "Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2018-08-19 15:33+0000\n"
+"POT-Creation-Date: 2018-09-15 23:22+0000\n"
 "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
 "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
 "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
 "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
 "Language-Team: LANGUAGE <LL@li.org>\n"
 "Language-Team: LANGUAGE <LL@li.org>\n"

BIN
misago/locale/es/LC_MESSAGES/django.mo


+ 30 - 34
misago/locale/es/LC_MESSAGES/django.po

@@ -8,7 +8,7 @@ msgid ""
 msgstr ""
 msgstr ""
 "Project-Id-Version: PACKAGE VERSION\n"
 "Project-Id-Version: PACKAGE VERSION\n"
 "Report-Msgid-Bugs-To: \n"
 "Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2018-08-19 15:33+0000\n"
+"POT-Creation-Date: 2018-09-15 23:22+0000\n"
 "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
 "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
 "Last-Translator: Carlos López <carlos@vocalaze.com>, 2017\n"
 "Last-Translator: Carlos López <carlos@vocalaze.com>, 2017\n"
 "Language-Team: Spanish (https://www.transifex.com/misago/teams/65369/es/)\n"
 "Language-Team: Spanish (https://www.transifex.com/misago/teams/65369/es/)\n"
@@ -142,19 +142,19 @@ msgstr "Debes seleccionar uno o más artículos."
 msgid "Action is not allowed."
 msgid "Action is not allowed."
 msgstr "La acción no está permitida"
 msgstr "La acción no está permitida"
 
 
-#: admin/views/index.py:83
-#, python-format
-msgid "Outdated: %(current)s! (latest: %(latest)s)"
-msgstr "Anticuado %(current)s! (último: %(latest)s)"
-
-#: admin/views/index.py:91
+#: admin/views/index.py:70
 #, python-format
 #, python-format
 msgid "Up to date! (%(current)s)"
 msgid "Up to date! (%(current)s)"
 msgstr "¡Actualizado! (%(current)s)"
 msgstr "¡Actualizado! (%(current)s)"
 
 
-#: admin/views/index.py:100
-msgid "Failed to connect to GitHub API. Try again later."
-msgstr "Error al conectarse a la API de GitHub. Inténtalo más tarde."
+#: admin/views/index.py:77
+#, python-format
+msgid "Outdated: %(current)s! (latest: %(latest)s)"
+msgstr "Anticuado %(current)s! (último: %(latest)s)"
+
+#: admin/views/index.py:87
+msgid "Failed to connect to pypi.org API. Try again later."
+msgstr ""
 
 
 #: categories/admin.py:47 templates/misago/categories/base.html:7
 #: categories/admin.py:47 templates/misago/categories/base.html:7
 #: templates/misago/categories/base.html:33
 #: templates/misago/categories/base.html:33
@@ -205,7 +205,7 @@ msgstr ""
 "Solo los miembros con permisos válidos pueden publicar en categorías "
 "Solo los miembros con permisos válidos pueden publicar en categorías "
 "cerradas."
 "cerradas."
 
 
-#: categories/forms.py:75 templates/misago/admin/index.html:110
+#: categories/forms.py:75 templates/misago/admin/index.html:103
 #: templates/misago/admin/users/delete.html:32 templates/misago/navbar.html:18
 #: templates/misago/admin/users/delete.html:32 templates/misago/navbar.html:18
 #: templates/misago/navbar.html:34 templates/misago/profile/threads.html:5
 #: templates/misago/navbar.html:34 templates/misago/profile/threads.html:5
 #: templates/misago/profile/threads.html:8
 #: templates/misago/profile/threads.html:8
@@ -547,7 +547,7 @@ msgstr "Sí"
 #: threads/permissions/threads.py:164 threads/permissions/threads.py:178
 #: threads/permissions/threads.py:164 threads/permissions/threads.py:178
 #: threads/permissions/threads.py:189 threads/permissions/threads.py:205
 #: threads/permissions/threads.py:189 threads/permissions/threads.py:205
 #: threads/permissions/threads.py:216 threads/permissions/threads.py:247
 #: threads/permissions/threads.py:216 threads/permissions/threads.py:247
-#: users/forms/admin.py:605 users/models/user.py:147
+#: users/forms/admin.py:605 users/models/user.py:143
 msgid "No"
 msgid "No"
 msgstr "No"
 msgstr "No"
 
 
@@ -1633,23 +1633,19 @@ msgstr ""
 msgid "The settings.py is missing MISAGO_ADDRESS value."
 msgid "The settings.py is missing MISAGO_ADDRESS value."
 msgstr ""
 msgstr ""
 
 
-#: templates/misago/admin/index.html:60
+#: templates/misago/admin/index.html:59
 msgid "Misago version"
 msgid "Misago version"
 msgstr "Versión de Misago"
 msgstr "Versión de Misago"
 
 
-#: templates/misago/admin/index.html:83
+#: templates/misago/admin/index.html:81
 msgid "Check version"
 msgid "Check version"
 msgstr "Comprobar Versión"
 msgstr "Comprobar Versión"
 
 
-#: templates/misago/admin/index.html:89
-msgid "This feature requires \"packaging\" python module."
-msgstr "Esta característica requiere el módulo \"paquete\" de python."
-
-#: templates/misago/admin/index.html:103
+#: templates/misago/admin/index.html:96
 msgid "DB Contents"
 msgid "DB Contents"
 msgstr "Contenido de BD"
 msgstr "Contenido de BD"
 
 
-#: templates/misago/admin/index.html:114
+#: templates/misago/admin/index.html:107
 #: templates/misago/admin/users/delete.html:36
 #: templates/misago/admin/users/delete.html:36
 #: templates/misago/admin/users/list.html:24
 #: templates/misago/admin/users/list.html:24
 #: templates/misago/profile/posts.html:5 templates/misago/profile/posts.html:8
 #: templates/misago/profile/posts.html:5 templates/misago/profile/posts.html:8
@@ -1658,7 +1654,7 @@ msgstr "Contenido de BD"
 msgid "Posts"
 msgid "Posts"
 msgstr "Mensajes"
 msgstr "Mensajes"
 
 
-#: templates/misago/admin/index.html:118 templates/misago/navbar.html:40
+#: templates/misago/admin/index.html:111 templates/misago/navbar.html:40
 #: templates/misago/userslists/base.html:5
 #: templates/misago/userslists/base.html:5
 #: templates/misago/userslists/base.html:14 users/admin.py:80
 #: templates/misago/userslists/base.html:14 users/admin.py:80
 #: users/migrations/0002_users_settings.py:16
 #: users/migrations/0002_users_settings.py:16
@@ -1666,11 +1662,11 @@ msgstr "Mensajes"
 msgid "Users"
 msgid "Users"
 msgstr "Usuarios"
 msgstr "Usuarios"
 
 
-#: templates/misago/admin/index.html:123
+#: templates/misago/admin/index.html:116
 msgid "Inactive users"
 msgid "Inactive users"
 msgstr "Usuarios inactivos"
 msgstr "Usuarios inactivos"
 
 
-#: templates/misago/admin/index.html:149
+#: templates/misago/admin/index.html:142
 msgid "Checking..."
 msgid "Checking..."
 msgstr "Comprobando..."
 msgstr "Comprobando..."
 
 
@@ -6308,7 +6304,7 @@ msgstr ""
 "entre mayúsculas y minúsculas."
 "entre mayúsculas y minúsculas."
 
 
 #: users/migrations/0004_default_ranks.py:17
 #: users/migrations/0004_default_ranks.py:17
-#: users/migrations/0004_default_ranks.py:18 users/models/user.py:113
+#: users/migrations/0004_default_ranks.py:18 users/models/user.py:109
 msgid "Forum team"
 msgid "Forum team"
 msgstr "Equipo del foro"
 msgstr "Equipo del foro"
 
 
@@ -6337,44 +6333,44 @@ msgstr ""
 msgid "User must have an email address."
 msgid "User must have an email address."
 msgstr "El usuario debe tener una dirección de correo electrónico."
 msgstr "El usuario debe tener una dirección de correo electrónico."
 
 
-#: users/models/user.py:148
+#: users/models/user.py:144
 msgid "Notify"
 msgid "Notify"
 msgstr "Notificar"
 msgstr "Notificar"
 
 
-#: users/models/user.py:149
+#: users/models/user.py:145
 msgid "Notify with e-mail"
 msgid "Notify with e-mail"
 msgstr "Notificar mediante e-mail"
 msgstr "Notificar mediante e-mail"
 
 
-#: users/models/user.py:157
+#: users/models/user.py:153
 msgid "Everybody"
 msgid "Everybody"
 msgstr "Todos"
 msgstr "Todos"
 
 
-#: users/models/user.py:158
+#: users/models/user.py:154
 msgid "Users I follow"
 msgid "Users I follow"
 msgstr "Usuarios que sigo"
 msgstr "Usuarios que sigo"
 
 
-#: users/models/user.py:159
+#: users/models/user.py:155
 msgid "Nobody"
 msgid "Nobody"
 msgstr "Nadie"
 msgstr "Nadie"
 
 
-#: users/models/user.py:177
+#: users/models/user.py:173
 msgid "joined on"
 msgid "joined on"
 msgstr "se unió a"
 msgstr "se unió a"
 
 
-#: users/models/user.py:191
+#: users/models/user.py:187
 msgid "staff status"
 msgid "staff status"
 msgstr "estado del personal"
 msgstr "estado del personal"
 
 
-#: users/models/user.py:193
+#: users/models/user.py:189
 msgid "Designates whether the user can log into admin sites."
 msgid "Designates whether the user can log into admin sites."
 msgstr ""
 msgstr ""
 "Designa si el usuario puede iniciar sesión en sitios de administración."
 "Designa si el usuario puede iniciar sesión en sitios de administración."
 
 
-#: users/models/user.py:200
+#: users/models/user.py:196
 msgid "active"
 msgid "active"
 msgstr "activo"
 msgstr "activo"
 
 
-#: users/models/user.py:204
+#: users/models/user.py:200
 msgid ""
 msgid ""
 "Designates whether this user should be treated as active. Unselect this "
 "Designates whether this user should be treated as active. Unselect this "
 "instead of deleting accounts."
 "instead of deleting accounts."

BIN
misago/locale/fr/LC_MESSAGES/django.mo


+ 30 - 34
misago/locale/fr/LC_MESSAGES/django.po

@@ -8,7 +8,7 @@ msgid ""
 msgstr ""
 msgstr ""
 "Project-Id-Version: PACKAGE VERSION\n"
 "Project-Id-Version: PACKAGE VERSION\n"
 "Report-Msgid-Bugs-To: \n"
 "Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2018-08-19 15:33+0000\n"
+"POT-Creation-Date: 2018-09-15 23:22+0000\n"
 "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
 "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
 "Last-Translator: carine hejl <carinelg@yahoo.fr>, 2017\n"
 "Last-Translator: carine hejl <carinelg@yahoo.fr>, 2017\n"
 "Language-Team: French (https://www.transifex.com/misago/teams/65369/fr/)\n"
 "Language-Team: French (https://www.transifex.com/misago/teams/65369/fr/)\n"
@@ -143,19 +143,19 @@ msgstr "Vous devez sélectionner au moins un élément."
 msgid "Action is not allowed."
 msgid "Action is not allowed."
 msgstr "Cette action n'est pas autorisée."
 msgstr "Cette action n'est pas autorisée."
 
 
-#: admin/views/index.py:83
-#, python-format
-msgid "Outdated: %(current)s! (latest: %(latest)s)"
-msgstr "Caduc : %(current)s ! (dernier : %(latest)s)"
-
-#: admin/views/index.py:91
+#: admin/views/index.py:70
 #, python-format
 #, python-format
 msgid "Up to date! (%(current)s)"
 msgid "Up to date! (%(current)s)"
 msgstr "Mis à jour ! (%(current)s)"
 msgstr "Mis à jour ! (%(current)s)"
 
 
-#: admin/views/index.py:100
-msgid "Failed to connect to GitHub API. Try again later."
-msgstr "Échec de la connexion à l'API GitHub. Réessayez plus tard."
+#: admin/views/index.py:77
+#, python-format
+msgid "Outdated: %(current)s! (latest: %(latest)s)"
+msgstr "Caduc : %(current)s ! (dernier : %(latest)s)"
+
+#: admin/views/index.py:87
+msgid "Failed to connect to pypi.org API. Try again later."
+msgstr ""
 
 
 #: categories/admin.py:47 templates/misago/categories/base.html:7
 #: categories/admin.py:47 templates/misago/categories/base.html:7
 #: templates/misago/categories/base.html:33
 #: templates/misago/categories/base.html:33
@@ -204,7 +204,7 @@ msgstr "Catégorie fermée"
 msgid "Only members with valid permissions can post in closed categories."
 msgid "Only members with valid permissions can post in closed categories."
 msgstr "Seul les membres autorisés peuvent écrire dans une catégorie close."
 msgstr "Seul les membres autorisés peuvent écrire dans une catégorie close."
 
 
-#: categories/forms.py:75 templates/misago/admin/index.html:110
+#: categories/forms.py:75 templates/misago/admin/index.html:103
 #: templates/misago/admin/users/delete.html:32 templates/misago/navbar.html:18
 #: templates/misago/admin/users/delete.html:32 templates/misago/navbar.html:18
 #: templates/misago/navbar.html:34 templates/misago/profile/threads.html:5
 #: templates/misago/navbar.html:34 templates/misago/profile/threads.html:5
 #: templates/misago/profile/threads.html:8
 #: templates/misago/profile/threads.html:8
@@ -553,7 +553,7 @@ msgstr "Oui"
 #: threads/permissions/threads.py:164 threads/permissions/threads.py:178
 #: threads/permissions/threads.py:164 threads/permissions/threads.py:178
 #: threads/permissions/threads.py:189 threads/permissions/threads.py:205
 #: threads/permissions/threads.py:189 threads/permissions/threads.py:205
 #: threads/permissions/threads.py:216 threads/permissions/threads.py:247
 #: threads/permissions/threads.py:216 threads/permissions/threads.py:247
-#: users/forms/admin.py:605 users/models/user.py:147
+#: users/forms/admin.py:605 users/models/user.py:143
 msgid "No"
 msgid "No"
 msgstr "Non"
 msgstr "Non"
 
 
@@ -1639,23 +1639,19 @@ msgstr ""
 msgid "The settings.py is missing MISAGO_ADDRESS value."
 msgid "The settings.py is missing MISAGO_ADDRESS value."
 msgstr ""
 msgstr ""
 
 
-#: templates/misago/admin/index.html:60
+#: templates/misago/admin/index.html:59
 msgid "Misago version"
 msgid "Misago version"
 msgstr "Version de Misago"
 msgstr "Version de Misago"
 
 
-#: templates/misago/admin/index.html:83
+#: templates/misago/admin/index.html:81
 msgid "Check version"
 msgid "Check version"
 msgstr "Vérifier la version"
 msgstr "Vérifier la version"
 
 
-#: templates/misago/admin/index.html:89
-msgid "This feature requires \"packaging\" python module."
-msgstr "Cette fonction requiert le module Python nommé « packaging »"
-
-#: templates/misago/admin/index.html:103
+#: templates/misago/admin/index.html:96
 msgid "DB Contents"
 msgid "DB Contents"
 msgstr "Contenu de la base de donnée"
 msgstr "Contenu de la base de donnée"
 
 
-#: templates/misago/admin/index.html:114
+#: templates/misago/admin/index.html:107
 #: templates/misago/admin/users/delete.html:36
 #: templates/misago/admin/users/delete.html:36
 #: templates/misago/admin/users/list.html:24
 #: templates/misago/admin/users/list.html:24
 #: templates/misago/profile/posts.html:5 templates/misago/profile/posts.html:8
 #: templates/misago/profile/posts.html:5 templates/misago/profile/posts.html:8
@@ -1664,7 +1660,7 @@ msgstr "Contenu de la base de donnée"
 msgid "Posts"
 msgid "Posts"
 msgstr "Messages"
 msgstr "Messages"
 
 
-#: templates/misago/admin/index.html:118 templates/misago/navbar.html:40
+#: templates/misago/admin/index.html:111 templates/misago/navbar.html:40
 #: templates/misago/userslists/base.html:5
 #: templates/misago/userslists/base.html:5
 #: templates/misago/userslists/base.html:14 users/admin.py:80
 #: templates/misago/userslists/base.html:14 users/admin.py:80
 #: users/migrations/0002_users_settings.py:16
 #: users/migrations/0002_users_settings.py:16
@@ -1672,11 +1668,11 @@ msgstr "Messages"
 msgid "Users"
 msgid "Users"
 msgstr "Utilisateurs"
 msgstr "Utilisateurs"
 
 
-#: templates/misago/admin/index.html:123
+#: templates/misago/admin/index.html:116
 msgid "Inactive users"
 msgid "Inactive users"
 msgstr "Utilisateurs inactifs"
 msgstr "Utilisateurs inactifs"
 
 
-#: templates/misago/admin/index.html:149
+#: templates/misago/admin/index.html:142
 msgid "Checking..."
 msgid "Checking..."
 msgstr "Vérification…"
 msgstr "Vérification…"
 
 
@@ -6558,7 +6554,7 @@ msgstr ""
 "affectées par la casse."
 "affectées par la casse."
 
 
 #: users/migrations/0004_default_ranks.py:17
 #: users/migrations/0004_default_ranks.py:17
-#: users/migrations/0004_default_ranks.py:18 users/models/user.py:113
+#: users/migrations/0004_default_ranks.py:18 users/models/user.py:109
 msgid "Forum team"
 msgid "Forum team"
 msgstr "Équipe du forum"
 msgstr "Équipe du forum"
 
 
@@ -6587,44 +6583,44 @@ msgstr ""
 msgid "User must have an email address."
 msgid "User must have an email address."
 msgstr "L'utilisateur doit avoir un courriel."
 msgstr "L'utilisateur doit avoir un courriel."
 
 
-#: users/models/user.py:148
+#: users/models/user.py:144
 msgid "Notify"
 msgid "Notify"
 msgstr "Notifier"
 msgstr "Notifier"
 
 
-#: users/models/user.py:149
+#: users/models/user.py:145
 msgid "Notify with e-mail"
 msgid "Notify with e-mail"
 msgstr "Notifier par courriel"
 msgstr "Notifier par courriel"
 
 
-#: users/models/user.py:157
+#: users/models/user.py:153
 msgid "Everybody"
 msgid "Everybody"
 msgstr "Tout le monde"
 msgstr "Tout le monde"
 
 
-#: users/models/user.py:158
+#: users/models/user.py:154
 msgid "Users I follow"
 msgid "Users I follow"
 msgstr "Utilisateurs que je suis"
 msgstr "Utilisateurs que je suis"
 
 
-#: users/models/user.py:159
+#: users/models/user.py:155
 msgid "Nobody"
 msgid "Nobody"
 msgstr "Personne"
 msgstr "Personne"
 
 
-#: users/models/user.py:177
+#: users/models/user.py:173
 msgid "joined on"
 msgid "joined on"
 msgstr "inscrit le"
 msgstr "inscrit le"
 
 
-#: users/models/user.py:191
+#: users/models/user.py:187
 msgid "staff status"
 msgid "staff status"
 msgstr "Statut du staff"
 msgstr "Statut du staff"
 
 
-#: users/models/user.py:193
+#: users/models/user.py:189
 msgid "Designates whether the user can log into admin sites."
 msgid "Designates whether the user can log into admin sites."
 msgstr ""
 msgstr ""
 "Indique si l'utilisateur peut se connecter sur les sites d'administration."
 "Indique si l'utilisateur peut se connecter sur les sites d'administration."
 
 
-#: users/models/user.py:200
+#: users/models/user.py:196
 msgid "active"
 msgid "active"
 msgstr "Actif"
 msgstr "Actif"
 
 
-#: users/models/user.py:204
+#: users/models/user.py:200
 msgid ""
 msgid ""
 "Designates whether this user should be treated as active. Unselect this "
 "Designates whether this user should be treated as active. Unselect this "
 "instead of deleting accounts."
 "instead of deleting accounts."

BIN
misago/locale/ru/LC_MESSAGES/django.mo


+ 30 - 34
misago/locale/ru/LC_MESSAGES/django.po

@@ -8,7 +8,7 @@ msgid ""
 msgstr ""
 msgstr ""
 "Project-Id-Version: PACKAGE VERSION\n"
 "Project-Id-Version: PACKAGE VERSION\n"
 "Report-Msgid-Bugs-To: \n"
 "Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2018-08-19 15:33+0000\n"
+"POT-Creation-Date: 2018-09-15 23:22+0000\n"
 "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
 "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
 "Last-Translator: alff0x1f <alff3one@gmail.com>, 2018\n"
 "Last-Translator: alff0x1f <alff3one@gmail.com>, 2018\n"
 "Language-Team: Russian (https://www.transifex.com/misago/teams/65369/ru/)\n"
 "Language-Team: Russian (https://www.transifex.com/misago/teams/65369/ru/)\n"
@@ -142,19 +142,19 @@ msgstr "Нужно выбрать один или более элементов.
 msgid "Action is not allowed."
 msgid "Action is not allowed."
 msgstr "Действие запрещено."
 msgstr "Действие запрещено."
 
 
-#: admin/views/index.py:83
-#, python-format
-msgid "Outdated: %(current)s! (latest: %(latest)s)"
-msgstr "Устаревшая: %(current)s! (свежая: %(latest)s)"
-
-#: admin/views/index.py:91
+#: admin/views/index.py:70
 #, python-format
 #, python-format
 msgid "Up to date! (%(current)s)"
 msgid "Up to date! (%(current)s)"
 msgstr "Самая свежая! (%(current)s)"
 msgstr "Самая свежая! (%(current)s)"
 
 
-#: admin/views/index.py:100
-msgid "Failed to connect to GitHub API. Try again later."
-msgstr "Не удалось соединиться с GitHub API. Попробуйте позднее."
+#: admin/views/index.py:77
+#, python-format
+msgid "Outdated: %(current)s! (latest: %(latest)s)"
+msgstr "Устаревшая: %(current)s! (свежая: %(latest)s)"
+
+#: admin/views/index.py:87
+msgid "Failed to connect to pypi.org API. Try again later."
+msgstr ""
 
 
 #: categories/admin.py:47 templates/misago/categories/base.html:7
 #: categories/admin.py:47 templates/misago/categories/base.html:7
 #: templates/misago/categories/base.html:33
 #: templates/misago/categories/base.html:33
@@ -205,7 +205,7 @@ msgstr ""
 "Только пользователи с соответствующими правами могут оставлять сообщения в "
 "Только пользователи с соответствующими правами могут оставлять сообщения в "
 "закрытых категориях."
 "закрытых категориях."
 
 
-#: categories/forms.py:75 templates/misago/admin/index.html:110
+#: categories/forms.py:75 templates/misago/admin/index.html:103
 #: templates/misago/admin/users/delete.html:32 templates/misago/navbar.html:18
 #: templates/misago/admin/users/delete.html:32 templates/misago/navbar.html:18
 #: templates/misago/navbar.html:34 templates/misago/profile/threads.html:5
 #: templates/misago/navbar.html:34 templates/misago/profile/threads.html:5
 #: templates/misago/profile/threads.html:8
 #: templates/misago/profile/threads.html:8
@@ -547,7 +547,7 @@ msgstr "Да"
 #: threads/permissions/threads.py:164 threads/permissions/threads.py:178
 #: threads/permissions/threads.py:164 threads/permissions/threads.py:178
 #: threads/permissions/threads.py:189 threads/permissions/threads.py:205
 #: threads/permissions/threads.py:189 threads/permissions/threads.py:205
 #: threads/permissions/threads.py:216 threads/permissions/threads.py:247
 #: threads/permissions/threads.py:216 threads/permissions/threads.py:247
-#: users/forms/admin.py:605 users/models/user.py:147
+#: users/forms/admin.py:605 users/models/user.py:143
 msgid "No"
 msgid "No"
 msgstr "Нет"
 msgstr "Нет"
 
 
@@ -1625,23 +1625,19 @@ msgstr ""
 msgid "The settings.py is missing MISAGO_ADDRESS value."
 msgid "The settings.py is missing MISAGO_ADDRESS value."
 msgstr ""
 msgstr ""
 
 
-#: templates/misago/admin/index.html:60
+#: templates/misago/admin/index.html:59
 msgid "Misago version"
 msgid "Misago version"
 msgstr "Версия Misago"
 msgstr "Версия Misago"
 
 
-#: templates/misago/admin/index.html:83
+#: templates/misago/admin/index.html:81
 msgid "Check version"
 msgid "Check version"
 msgstr "Проверить версию"
 msgstr "Проверить версию"
 
 
-#: templates/misago/admin/index.html:89
-msgid "This feature requires \"packaging\" python module."
-msgstr "Эта функция требует python модуль \"packaging\"."
-
-#: templates/misago/admin/index.html:103
+#: templates/misago/admin/index.html:96
 msgid "DB Contents"
 msgid "DB Contents"
 msgstr "Содержимое базы данных"
 msgstr "Содержимое базы данных"
 
 
-#: templates/misago/admin/index.html:114
+#: templates/misago/admin/index.html:107
 #: templates/misago/admin/users/delete.html:36
 #: templates/misago/admin/users/delete.html:36
 #: templates/misago/admin/users/list.html:24
 #: templates/misago/admin/users/list.html:24
 #: templates/misago/profile/posts.html:5 templates/misago/profile/posts.html:8
 #: templates/misago/profile/posts.html:5 templates/misago/profile/posts.html:8
@@ -1650,7 +1646,7 @@ msgstr "Содержимое базы данных"
 msgid "Posts"
 msgid "Posts"
 msgstr "Сообщения"
 msgstr "Сообщения"
 
 
-#: templates/misago/admin/index.html:118 templates/misago/navbar.html:40
+#: templates/misago/admin/index.html:111 templates/misago/navbar.html:40
 #: templates/misago/userslists/base.html:5
 #: templates/misago/userslists/base.html:5
 #: templates/misago/userslists/base.html:14 users/admin.py:80
 #: templates/misago/userslists/base.html:14 users/admin.py:80
 #: users/migrations/0002_users_settings.py:16
 #: users/migrations/0002_users_settings.py:16
@@ -1658,11 +1654,11 @@ msgstr "Сообщения"
 msgid "Users"
 msgid "Users"
 msgstr "Пользователи"
 msgstr "Пользователи"
 
 
-#: templates/misago/admin/index.html:123
+#: templates/misago/admin/index.html:116
 msgid "Inactive users"
 msgid "Inactive users"
 msgstr "Деактивированные пользователи"
 msgstr "Деактивированные пользователи"
 
 
-#: templates/misago/admin/index.html:149
+#: templates/misago/admin/index.html:142
 msgid "Checking..."
 msgid "Checking..."
 msgstr "Проверка..."
 msgstr "Проверка..."
 
 
@@ -6453,7 +6449,7 @@ msgid "Enter each answer in new line. Answers are case-insensitive."
 msgstr "Введите каждый ответ в новой строке. Ответы чуствительны к регистру."
 msgstr "Введите каждый ответ в новой строке. Ответы чуствительны к регистру."
 
 
 #: users/migrations/0004_default_ranks.py:17
 #: users/migrations/0004_default_ranks.py:17
-#: users/migrations/0004_default_ranks.py:18 users/models/user.py:113
+#: users/migrations/0004_default_ranks.py:18 users/models/user.py:109
 msgid "Forum team"
 msgid "Forum team"
 msgstr "Форум команды"
 msgstr "Форум команды"
 
 
@@ -6482,43 +6478,43 @@ msgstr ""
 msgid "User must have an email address."
 msgid "User must have an email address."
 msgstr "Пользователь обязан иметь e-mail адрес."
 msgstr "Пользователь обязан иметь e-mail адрес."
 
 
-#: users/models/user.py:148
+#: users/models/user.py:144
 msgid "Notify"
 msgid "Notify"
 msgstr "Уведомлять"
 msgstr "Уведомлять"
 
 
-#: users/models/user.py:149
+#: users/models/user.py:145
 msgid "Notify with e-mail"
 msgid "Notify with e-mail"
 msgstr "Уведомлять по e-mail"
 msgstr "Уведомлять по e-mail"
 
 
-#: users/models/user.py:157
+#: users/models/user.py:153
 msgid "Everybody"
 msgid "Everybody"
 msgstr "Все"
 msgstr "Все"
 
 
-#: users/models/user.py:158
+#: users/models/user.py:154
 msgid "Users I follow"
 msgid "Users I follow"
 msgstr "Пользователи, которых я читаю"
 msgstr "Пользователи, которых я читаю"
 
 
-#: users/models/user.py:159
+#: users/models/user.py:155
 msgid "Nobody"
 msgid "Nobody"
 msgstr "Никто"
 msgstr "Никто"
 
 
-#: users/models/user.py:177
+#: users/models/user.py:173
 msgid "joined on"
 msgid "joined on"
 msgstr "присоединился к"
 msgstr "присоединился к"
 
 
-#: users/models/user.py:191
+#: users/models/user.py:187
 msgid "staff status"
 msgid "staff status"
 msgstr "статус сотрудника"
 msgstr "статус сотрудника"
 
 
-#: users/models/user.py:193
+#: users/models/user.py:189
 msgid "Designates whether the user can log into admin sites."
 msgid "Designates whether the user can log into admin sites."
 msgstr "Указывает, может ли пользователь заходить на администраторские сайты."
 msgstr "Указывает, может ли пользователь заходить на администраторские сайты."
 
 
-#: users/models/user.py:200
+#: users/models/user.py:196
 msgid "active"
 msgid "active"
 msgstr "активен"
 msgstr "активен"
 
 
-#: users/models/user.py:204
+#: users/models/user.py:200
 msgid ""
 msgid ""
 "Designates whether this user should be treated as active. Unselect this "
 "Designates whether this user should be treated as active. Unselect this "
 "instead of deleting accounts."
 "instead of deleting accounts."

BIN
misago/locale/tr/LC_MESSAGES/django.mo


+ 30 - 34
misago/locale/tr/LC_MESSAGES/django.po

@@ -8,7 +8,7 @@ msgid ""
 msgstr ""
 msgstr ""
 "Project-Id-Version: PACKAGE VERSION\n"
 "Project-Id-Version: PACKAGE VERSION\n"
 "Report-Msgid-Bugs-To: \n"
 "Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2018-08-19 15:33+0000\n"
+"POT-Creation-Date: 2018-09-15 23:22+0000\n"
 "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
 "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
 "Last-Translator: Yiğitcan Uçan <ucanyiit@gmail.com>, 2017\n"
 "Last-Translator: Yiğitcan Uçan <ucanyiit@gmail.com>, 2017\n"
 "Language-Team: Turkish (Turkey) (https://www.transifex.com/misago/teams/65369/tr_TR/)\n"
 "Language-Team: Turkish (Turkey) (https://www.transifex.com/misago/teams/65369/tr_TR/)\n"
@@ -142,19 +142,19 @@ msgstr "Bir veya daha fazla öğe seçmeniz gerekiyor."
 msgid "Action is not allowed."
 msgid "Action is not allowed."
 msgstr "İşlem yapılmasına izin verilmiyor."
 msgstr "İşlem yapılmasına izin verilmiyor."
 
 
-#: admin/views/index.py:83
-#, python-format
-msgid "Outdated: %(current)s! (latest: %(latest)s)"
-msgstr "Güncel olmayan:%(current)s! (en son:%(latest)s)"
-
-#: admin/views/index.py:91
+#: admin/views/index.py:70
 #, python-format
 #, python-format
 msgid "Up to date! (%(current)s)"
 msgid "Up to date! (%(current)s)"
 msgstr "Güncel! (%(current)s)"
 msgstr "Güncel! (%(current)s)"
 
 
-#: admin/views/index.py:100
-msgid "Failed to connect to GitHub API. Try again later."
-msgstr "GitHub API'ye bağlanti hatasi.Lütfen tekrar deneyin"
+#: admin/views/index.py:77
+#, python-format
+msgid "Outdated: %(current)s! (latest: %(latest)s)"
+msgstr "Güncel olmayan:%(current)s! (en son:%(latest)s)"
+
+#: admin/views/index.py:87
+msgid "Failed to connect to pypi.org API. Try again later."
+msgstr ""
 
 
 #: categories/admin.py:47 templates/misago/categories/base.html:7
 #: categories/admin.py:47 templates/misago/categories/base.html:7
 #: templates/misago/categories/base.html:33
 #: templates/misago/categories/base.html:33
@@ -201,7 +201,7 @@ msgstr "Kapalı bölüm"
 msgid "Only members with valid permissions can post in closed categories."
 msgid "Only members with valid permissions can post in closed categories."
 msgstr "Sadece geçerli izni olan üyeler kapalı kategorilerde ileti yapabilir."
 msgstr "Sadece geçerli izni olan üyeler kapalı kategorilerde ileti yapabilir."
 
 
-#: categories/forms.py:75 templates/misago/admin/index.html:110
+#: categories/forms.py:75 templates/misago/admin/index.html:103
 #: templates/misago/admin/users/delete.html:32 templates/misago/navbar.html:18
 #: templates/misago/admin/users/delete.html:32 templates/misago/navbar.html:18
 #: templates/misago/navbar.html:34 templates/misago/profile/threads.html:5
 #: templates/misago/navbar.html:34 templates/misago/profile/threads.html:5
 #: templates/misago/profile/threads.html:8
 #: templates/misago/profile/threads.html:8
@@ -538,7 +538,7 @@ msgstr "Evet"
 #: threads/permissions/threads.py:164 threads/permissions/threads.py:178
 #: threads/permissions/threads.py:164 threads/permissions/threads.py:178
 #: threads/permissions/threads.py:189 threads/permissions/threads.py:205
 #: threads/permissions/threads.py:189 threads/permissions/threads.py:205
 #: threads/permissions/threads.py:216 threads/permissions/threads.py:247
 #: threads/permissions/threads.py:216 threads/permissions/threads.py:247
-#: users/forms/admin.py:605 users/models/user.py:147
+#: users/forms/admin.py:605 users/models/user.py:143
 msgid "No"
 msgid "No"
 msgstr "Hayır"
 msgstr "Hayır"
 
 
@@ -1610,23 +1610,19 @@ msgstr ""
 msgid "The settings.py is missing MISAGO_ADDRESS value."
 msgid "The settings.py is missing MISAGO_ADDRESS value."
 msgstr ""
 msgstr ""
 
 
-#: templates/misago/admin/index.html:60
+#: templates/misago/admin/index.html:59
 msgid "Misago version"
 msgid "Misago version"
 msgstr "Misago sürümü"
 msgstr "Misago sürümü"
 
 
-#: templates/misago/admin/index.html:83
+#: templates/misago/admin/index.html:81
 msgid "Check version"
 msgid "Check version"
 msgstr "Sürümü kontrol et"
 msgstr "Sürümü kontrol et"
 
 
-#: templates/misago/admin/index.html:89
-msgid "This feature requires \"packaging\" python module."
-msgstr "Bu özellik \"paketleme\" python modülünü gerektirir."
-
-#: templates/misago/admin/index.html:103
+#: templates/misago/admin/index.html:96
 msgid "DB Contents"
 msgid "DB Contents"
 msgstr "DB içerikleri"
 msgstr "DB içerikleri"
 
 
-#: templates/misago/admin/index.html:114
+#: templates/misago/admin/index.html:107
 #: templates/misago/admin/users/delete.html:36
 #: templates/misago/admin/users/delete.html:36
 #: templates/misago/admin/users/list.html:24
 #: templates/misago/admin/users/list.html:24
 #: templates/misago/profile/posts.html:5 templates/misago/profile/posts.html:8
 #: templates/misago/profile/posts.html:5 templates/misago/profile/posts.html:8
@@ -1635,7 +1631,7 @@ msgstr "DB içerikleri"
 msgid "Posts"
 msgid "Posts"
 msgstr "iletiler"
 msgstr "iletiler"
 
 
-#: templates/misago/admin/index.html:118 templates/misago/navbar.html:40
+#: templates/misago/admin/index.html:111 templates/misago/navbar.html:40
 #: templates/misago/userslists/base.html:5
 #: templates/misago/userslists/base.html:5
 #: templates/misago/userslists/base.html:14 users/admin.py:80
 #: templates/misago/userslists/base.html:14 users/admin.py:80
 #: users/migrations/0002_users_settings.py:16
 #: users/migrations/0002_users_settings.py:16
@@ -1643,11 +1639,11 @@ msgstr "iletiler"
 msgid "Users"
 msgid "Users"
 msgstr "Kullanicilar"
 msgstr "Kullanicilar"
 
 
-#: templates/misago/admin/index.html:123
+#: templates/misago/admin/index.html:116
 msgid "Inactive users"
 msgid "Inactive users"
 msgstr "Aktif olmayan kullanicilar"
 msgstr "Aktif olmayan kullanicilar"
 
 
-#: templates/misago/admin/index.html:149
+#: templates/misago/admin/index.html:142
 msgid "Checking..."
 msgid "Checking..."
 msgstr "Kontrol ediliyor..."
 msgstr "Kontrol ediliyor..."
 
 
@@ -6245,7 +6241,7 @@ msgstr ""
 "Her yanıtı yeni satıra yazın. Yanıtlar büyük / küçük harf duyarlı değildir."
 "Her yanıtı yeni satıra yazın. Yanıtlar büyük / küçük harf duyarlı değildir."
 
 
 #: users/migrations/0004_default_ranks.py:17
 #: users/migrations/0004_default_ranks.py:17
-#: users/migrations/0004_default_ranks.py:18 users/models/user.py:113
+#: users/migrations/0004_default_ranks.py:18 users/models/user.py:109
 msgid "Forum team"
 msgid "Forum team"
 msgstr "Forum ekibi"
 msgstr "Forum ekibi"
 
 
@@ -6274,44 +6270,44 @@ msgstr ""
 msgid "User must have an email address."
 msgid "User must have an email address."
 msgstr "Kullanıcının bir e-posta adresine sahip olması gerekir."
 msgstr "Kullanıcının bir e-posta adresine sahip olması gerekir."
 
 
-#: users/models/user.py:148
+#: users/models/user.py:144
 msgid "Notify"
 msgid "Notify"
 msgstr "bildirmek"
 msgstr "bildirmek"
 
 
-#: users/models/user.py:149
+#: users/models/user.py:145
 msgid "Notify with e-mail"
 msgid "Notify with e-mail"
 msgstr "E-posta ile bildir"
 msgstr "E-posta ile bildir"
 
 
-#: users/models/user.py:157
+#: users/models/user.py:153
 msgid "Everybody"
 msgid "Everybody"
 msgstr "herkes"
 msgstr "herkes"
 
 
-#: users/models/user.py:158
+#: users/models/user.py:154
 msgid "Users I follow"
 msgid "Users I follow"
 msgstr "Takip ettiğim kullanıcılar"
 msgstr "Takip ettiğim kullanıcılar"
 
 
-#: users/models/user.py:159
+#: users/models/user.py:155
 msgid "Nobody"
 msgid "Nobody"
 msgstr "Kimse"
 msgstr "Kimse"
 
 
-#: users/models/user.py:177
+#: users/models/user.py:173
 msgid "joined on"
 msgid "joined on"
 msgstr "üzerine katıldı"
 msgstr "üzerine katıldı"
 
 
-#: users/models/user.py:191
+#: users/models/user.py:187
 msgid "staff status"
 msgid "staff status"
 msgstr "personel durumu"
 msgstr "personel durumu"
 
 
-#: users/models/user.py:193
+#: users/models/user.py:189
 msgid "Designates whether the user can log into admin sites."
 msgid "Designates whether the user can log into admin sites."
 msgstr ""
 msgstr ""
 "Kullanıcının admin sitelerine giriş yapıp giriş yapamayacağını belirtir."
 "Kullanıcının admin sitelerine giriş yapıp giriş yapamayacağını belirtir."
 
 
-#: users/models/user.py:200
+#: users/models/user.py:196
 msgid "active"
 msgid "active"
 msgstr "aktif"
 msgstr "aktif"
 
 
-#: users/models/user.py:204
+#: users/models/user.py:200
 msgid ""
 msgid ""
 "Designates whether this user should be treated as active. Unselect this "
 "Designates whether this user should be treated as active. Unselect this "
 "instead of deleting accounts."
 "instead of deleting accounts."

BIN
misago/locale/zh_Hans/LC_MESSAGES/django.mo


+ 41 - 45
misago/locale/zh_Hans/LC_MESSAGES/django.po

@@ -8,9 +8,9 @@ msgid ""
 msgstr ""
 msgstr ""
 "Project-Id-Version: PACKAGE VERSION\n"
 "Project-Id-Version: PACKAGE VERSION\n"
 "Report-Msgid-Bugs-To: \n"
 "Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2018-08-19 15:33+0000\n"
+"POT-Creation-Date: 2018-09-15 23:22+0000\n"
 "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
 "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
-"Last-Translator: cxgreat2014 <fwy1998@gmail.com>, 2018\n"
+"Last-Translator: 沐子白 <yiveco@qq.com>, 2018\n"
 "Language-Team: Chinese (China) (https://www.transifex.com/misago/teams/65369/zh_CN/)\n"
 "Language-Team: Chinese (China) (https://www.transifex.com/misago/teams/65369/zh_CN/)\n"
 "MIME-Version: 1.0\n"
 "MIME-Version: 1.0\n"
 "Content-Type: text/plain; charset=UTF-8\n"
 "Content-Type: text/plain; charset=UTF-8\n"
@@ -142,19 +142,19 @@ msgstr "您必须选择一个或者多个项目"
 msgid "Action is not allowed."
 msgid "Action is not allowed."
 msgstr "操作不被许可"
 msgstr "操作不被许可"
 
 
-#: admin/views/index.py:83
-#, python-format
-msgid "Outdated: %(current)s! (latest: %(latest)s)"
-msgstr "已过期:%(current)s!(最新:%(latest)s)"
-
-#: admin/views/index.py:91
+#: admin/views/index.py:70
 #, python-format
 #, python-format
 msgid "Up to date! (%(current)s)"
 msgid "Up to date! (%(current)s)"
 msgstr "更新到 (%(current)s)"
 msgstr "更新到 (%(current)s)"
 
 
-#: admin/views/index.py:100
-msgid "Failed to connect to GitHub API. Try again later."
-msgstr "访问GitHub API失败,请稍候尝试"
+#: admin/views/index.py:77
+#, python-format
+msgid "Outdated: %(current)s! (latest: %(latest)s)"
+msgstr "已过期:%(current)s!(最新:%(latest)s)"
+
+#: admin/views/index.py:87
+msgid "Failed to connect to pypi.org API. Try again later."
+msgstr ""
 
 
 #: categories/admin.py:47 templates/misago/categories/base.html:7
 #: categories/admin.py:47 templates/misago/categories/base.html:7
 #: templates/misago/categories/base.html:33
 #: templates/misago/categories/base.html:33
@@ -201,7 +201,7 @@ msgstr "已关闭版块"
 msgid "Only members with valid permissions can post in closed categories."
 msgid "Only members with valid permissions can post in closed categories."
 msgstr "只有具备特定权限的成员可以在已经关闭的版块中发帖。"
 msgstr "只有具备特定权限的成员可以在已经关闭的版块中发帖。"
 
 
-#: categories/forms.py:75 templates/misago/admin/index.html:110
+#: categories/forms.py:75 templates/misago/admin/index.html:103
 #: templates/misago/admin/users/delete.html:32 templates/misago/navbar.html:18
 #: templates/misago/admin/users/delete.html:32 templates/misago/navbar.html:18
 #: templates/misago/navbar.html:34 templates/misago/profile/threads.html:5
 #: templates/misago/navbar.html:34 templates/misago/profile/threads.html:5
 #: templates/misago/profile/threads.html:8
 #: templates/misago/profile/threads.html:8
@@ -358,11 +358,11 @@ msgstr "在审查队列中"
 
 
 #: categories/migrations/0007_best_answers_roles.py:15
 #: categories/migrations/0007_best_answers_roles.py:15
 msgid "Q&A user"
 msgid "Q&A user"
-msgstr ""
+msgstr "问答用户"
 
 
 #: categories/migrations/0007_best_answers_roles.py:27
 #: categories/migrations/0007_best_answers_roles.py:27
 msgid "Q&A moderator"
 msgid "Q&A moderator"
-msgstr ""
+msgstr "问答主持人"
 
 
 #: categories/permissions.py:16
 #: categories/permissions.py:16
 msgid "Category access"
 msgid "Category access"
@@ -525,7 +525,7 @@ msgstr "是"
 #: threads/permissions/threads.py:164 threads/permissions/threads.py:178
 #: threads/permissions/threads.py:164 threads/permissions/threads.py:178
 #: threads/permissions/threads.py:189 threads/permissions/threads.py:205
 #: threads/permissions/threads.py:189 threads/permissions/threads.py:205
 #: threads/permissions/threads.py:216 threads/permissions/threads.py:247
 #: threads/permissions/threads.py:216 threads/permissions/threads.py:247
-#: users/forms/admin.py:605 users/models/user.py:147
+#: users/forms/admin.py:605 users/models/user.py:143
 msgid "No"
 msgid "No"
 msgstr "否"
 msgstr "否"
 
 
@@ -625,7 +625,7 @@ msgstr "过长"
 
 
 #: legal/admin.py:25 templates/misago/admin/users/edit.html:216
 #: legal/admin.py:25 templates/misago/admin/users/edit.html:216
 msgid "Agreements"
 msgid "Agreements"
-msgstr ""
+msgstr "协议"
 
 
 #: legal/api.py:18
 #: legal/api.py:18
 msgid "You have already accepted this agreement."
 msgid "You have already accepted this agreement."
@@ -665,7 +665,7 @@ msgstr ""
 
 
 #: legal/forms.py:27
 #: legal/forms.py:27
 msgid "Link"
 msgid "Link"
-msgstr ""
+msgstr "链接"
 
 
 #: legal/forms.py:28
 #: legal/forms.py:28
 msgid "If your agreement is located on other page, enter here a link to it."
 msgid "If your agreement is located on other page, enter here a link to it."
@@ -973,11 +973,11 @@ msgstr ""
 
 
 #: templates/misago/admin/agreements/list.html:19
 #: templates/misago/admin/agreements/list.html:19
 msgid "Created"
 msgid "Created"
-msgstr ""
+msgstr "创建"
 
 
 #: templates/misago/admin/agreements/list.html:20
 #: templates/misago/admin/agreements/list.html:20
 msgid "Modified"
 msgid "Modified"
-msgstr ""
+msgstr "修改"
 
 
 #: templates/misago/admin/agreements/list.html:33
 #: templates/misago/admin/agreements/list.html:33
 #, python-format
 #, python-format
@@ -1344,11 +1344,11 @@ msgstr "更改设置"
 #: templates/misago/admin/datadownloads/form.html:11
 #: templates/misago/admin/datadownloads/form.html:11
 #: templates/misago/admin/datadownloads/form.html:17
 #: templates/misago/admin/datadownloads/form.html:17
 msgid "Request new data downloads"
 msgid "Request new data downloads"
-msgstr ""
+msgstr "请求新的数据下载"
 
 
 #: templates/misago/admin/datadownloads/list.html:9
 #: templates/misago/admin/datadownloads/list.html:9
 msgid "Request new downloads"
 msgid "Request new downloads"
-msgstr ""
+msgstr "请求新的下载"
 
 
 #: templates/misago/admin/datadownloads/list.html:17
 #: templates/misago/admin/datadownloads/list.html:17
 #: templates/misago/admin/users/list.html:17 users/forms/admin.py:696
 #: templates/misago/admin/users/list.html:17 users/forms/admin.py:696
@@ -1366,7 +1366,7 @@ msgstr ""
 
 
 #: templates/misago/admin/datadownloads/list.html:21 users/forms/admin.py:700
 #: templates/misago/admin/datadownloads/list.html:21 users/forms/admin.py:700
 msgid "Requested by"
 msgid "Requested by"
-msgstr ""
+msgstr "请求来源"
 
 
 #: templates/misago/admin/datadownloads/list.html:32
 #: templates/misago/admin/datadownloads/list.html:32
 #: templates/misago/admin/datadownloads/list.html:49
 #: templates/misago/admin/datadownloads/list.html:49
@@ -1381,7 +1381,7 @@ msgstr "头像"
 #: templates/misago/admin/datadownloads/list.html:73
 #: templates/misago/admin/datadownloads/list.html:73
 #: templates/misago/emails/data_download.html:11 users/apps.py:50
 #: templates/misago/emails/data_download.html:11 users/apps.py:50
 msgid "Download data"
 msgid "Download data"
-msgstr ""
+msgstr "下载数据"
 
 
 #: templates/misago/admin/datadownloads/list.html:84
 #: templates/misago/admin/datadownloads/list.html:84
 msgid "No data downloads matching search criteria have been found."
 msgid "No data downloads matching search criteria have been found."
@@ -1579,23 +1579,19 @@ msgstr ""
 msgid "The settings.py is missing MISAGO_ADDRESS value."
 msgid "The settings.py is missing MISAGO_ADDRESS value."
 msgstr ""
 msgstr ""
 
 
-#: templates/misago/admin/index.html:60
+#: templates/misago/admin/index.html:59
 msgid "Misago version"
 msgid "Misago version"
 msgstr "论坛框架版本"
 msgstr "论坛框架版本"
 
 
-#: templates/misago/admin/index.html:83
+#: templates/misago/admin/index.html:81
 msgid "Check version"
 msgid "Check version"
 msgstr "检查版本"
 msgstr "检查版本"
 
 
-#: templates/misago/admin/index.html:89
-msgid "This feature requires \"packaging\" python module."
-msgstr "此功能需要\"packaging\" Python模块"
-
-#: templates/misago/admin/index.html:103
+#: templates/misago/admin/index.html:96
 msgid "DB Contents"
 msgid "DB Contents"
 msgstr "数据库内容"
 msgstr "数据库内容"
 
 
-#: templates/misago/admin/index.html:114
+#: templates/misago/admin/index.html:107
 #: templates/misago/admin/users/delete.html:36
 #: templates/misago/admin/users/delete.html:36
 #: templates/misago/admin/users/list.html:24
 #: templates/misago/admin/users/list.html:24
 #: templates/misago/profile/posts.html:5 templates/misago/profile/posts.html:8
 #: templates/misago/profile/posts.html:5 templates/misago/profile/posts.html:8
@@ -1604,7 +1600,7 @@ msgstr "数据库内容"
 msgid "Posts"
 msgid "Posts"
 msgstr "发帖"
 msgstr "发帖"
 
 
-#: templates/misago/admin/index.html:118 templates/misago/navbar.html:40
+#: templates/misago/admin/index.html:111 templates/misago/navbar.html:40
 #: templates/misago/userslists/base.html:5
 #: templates/misago/userslists/base.html:5
 #: templates/misago/userslists/base.html:14 users/admin.py:80
 #: templates/misago/userslists/base.html:14 users/admin.py:80
 #: users/migrations/0002_users_settings.py:16
 #: users/migrations/0002_users_settings.py:16
@@ -1612,11 +1608,11 @@ msgstr "发帖"
 msgid "Users"
 msgid "Users"
 msgstr "用户"
 msgstr "用户"
 
 
-#: templates/misago/admin/index.html:123
+#: templates/misago/admin/index.html:116
 msgid "Inactive users"
 msgid "Inactive users"
 msgstr "冻结用户"
 msgstr "冻结用户"
 
 
-#: templates/misago/admin/index.html:149
+#: templates/misago/admin/index.html:142
 msgid "Checking..."
 msgid "Checking..."
 msgstr "检查中…"
 msgstr "检查中…"
 
 
@@ -5973,7 +5969,7 @@ msgid "Enter each answer in new line. Answers are case-insensitive."
 msgstr "在新行中输入每个答案。答案不区分大小写。"
 msgstr "在新行中输入每个答案。答案不区分大小写。"
 
 
 #: users/migrations/0004_default_ranks.py:17
 #: users/migrations/0004_default_ranks.py:17
-#: users/migrations/0004_default_ranks.py:18 users/models/user.py:113
+#: users/migrations/0004_default_ranks.py:18 users/models/user.py:109
 msgid "Forum team"
 msgid "Forum team"
 msgstr "论坛团队"
 msgstr "论坛团队"
 
 
@@ -6002,43 +5998,43 @@ msgstr ""
 msgid "User must have an email address."
 msgid "User must have an email address."
 msgstr "用户必须有一个电子邮件地址。"
 msgstr "用户必须有一个电子邮件地址。"
 
 
-#: users/models/user.py:148
+#: users/models/user.py:144
 msgid "Notify"
 msgid "Notify"
 msgstr "通知"
 msgstr "通知"
 
 
-#: users/models/user.py:149
+#: users/models/user.py:145
 msgid "Notify with e-mail"
 msgid "Notify with e-mail"
 msgstr "通过电子邮件通知"
 msgstr "通过电子邮件通知"
 
 
-#: users/models/user.py:157
+#: users/models/user.py:153
 msgid "Everybody"
 msgid "Everybody"
 msgstr "大家"
 msgstr "大家"
 
 
-#: users/models/user.py:158
+#: users/models/user.py:154
 msgid "Users I follow"
 msgid "Users I follow"
 msgstr "我关注的用户"
 msgstr "我关注的用户"
 
 
-#: users/models/user.py:159
+#: users/models/user.py:155
 msgid "Nobody"
 msgid "Nobody"
 msgstr "没有人"
 msgstr "没有人"
 
 
-#: users/models/user.py:177
+#: users/models/user.py:173
 msgid "joined on"
 msgid "joined on"
 msgstr "加入了"
 msgstr "加入了"
 
 
-#: users/models/user.py:191
+#: users/models/user.py:187
 msgid "staff status"
 msgid "staff status"
 msgstr "员工状态"
 msgstr "员工状态"
 
 
-#: users/models/user.py:193
+#: users/models/user.py:189
 msgid "Designates whether the user can log into admin sites."
 msgid "Designates whether the user can log into admin sites."
 msgstr "指定用户是否可以登录管理站点。"
 msgstr "指定用户是否可以登录管理站点。"
 
 
-#: users/models/user.py:200
+#: users/models/user.py:196
 msgid "active"
 msgid "active"
 msgstr "未冻结"
 msgstr "未冻结"
 
 
-#: users/models/user.py:204
+#: users/models/user.py:200
 msgid ""
 msgid ""
 "Designates whether this user should be treated as active. Unselect this "
 "Designates whether this user should be treated as active. Unselect this "
 "instead of deleting accounts."
 "instead of deleting accounts."

+ 1 - 1
misago/markup/parser.py

@@ -172,7 +172,7 @@ def clean_links(request, result, force_shva=False):
         else:
         else:
             result['outgoing_links'].append(clean_link_prefix(link['href']))
             result['outgoing_links'].append(clean_link_prefix(link['href']))
             link['href'] = assert_link_prefix(link['href'])
             link['href'] = assert_link_prefix(link['href'])
-            link['rel'] = 'nofollow'
+            link['rel'] = 'nofollow noopener'
 
 
         if link.string:
         if link.string:
             link.string = clean_link_prefix(link.string)
             link.string = clean_link_prefix(link.string)

+ 6 - 6
misago/markup/tests/test_parser.py

@@ -127,10 +127,10 @@ Lorem ipsum [Lorem ipsum](https://placekitten.com/g/1200/500)
 """.strip()
 """.strip()
 
 
         expected_result = """
         expected_result = """
-<p>Lorem ipsum <a href="http://placekitten.com/g/300/300" rel="nofollow">placekitten.com/g/300/300</a></p>
-<p>Lorem ipsum <a href="https://placekitten.com/g/600/600" rel="nofollow">placekitten.com/g/600/600</a></p>
-<p>Lorem ipsum <a href="https://placekitten.com/g/400/400" rel="nofollow">Label text!</a></p>
-<p>Lorem ipsum <a href="https://placekitten.com/g/1200/500" rel="nofollow">Lorem ipsum</a></p>
+<p>Lorem ipsum <a href="http://placekitten.com/g/300/300" rel="nofollow noopener">placekitten.com/g/300/300</a></p>
+<p>Lorem ipsum <a href="https://placekitten.com/g/600/600" rel="nofollow noopener">placekitten.com/g/600/600</a></p>
+<p>Lorem ipsum <a href="https://placekitten.com/g/400/400" rel="nofollow noopener">Label text!</a></p>
+<p>Lorem ipsum <a href="https://placekitten.com/g/1200/500" rel="nofollow noopener">Lorem ipsum</a></p>
 """.strip()
 """.strip()
 
 
         result = parse(test_text, MockRequest(), MockPoster(), minify=False)
         result = parse(test_text, MockRequest(), MockPoster(), minify=False)
@@ -240,7 +240,7 @@ Lorem ipsum: http://somewhere.com
 """.strip()
 """.strip()
 
 
         expected_result = """
         expected_result = """
-<p>Lorem ipsum: <a href="http://somewhere.com" rel="nofollow">somewhere.com</a></p>
+<p>Lorem ipsum: <a href="http://somewhere.com" rel="nofollow noopener">somewhere.com</a></p>
 """.strip()
 """.strip()
 
 
         result = parse(test_text, MockRequest(), MockPoster(), minify=True)
         result = parse(test_text, MockRequest(), MockPoster(), minify=True)
@@ -256,7 +256,7 @@ Lorem ipsum: http://somewhere.com/somewhere-something/
 """.strip()
 """.strip()
 
 
         expected_result = """
         expected_result = """
-<p>Lorem ipsum: <a href="http://somewhere.com/somewhere-something/" rel="nofollow">somewhere.com/somewhere-something/</a></p>
+<p>Lorem ipsum: <a href="http://somewhere.com/somewhere-something/" rel="nofollow noopener">somewhere.com/somewhere-something/</a></p>
 """.strip()
 """.strip()
 
 
         result = parse(test_text, MockRequest(), MockPoster(), minify=True)
         result = parse(test_text, MockRequest(), MockPoster(), minify=True)

+ 10 - 10
misago/project_template/cron.txt

@@ -2,15 +2,15 @@
 # you'll likely need to change it to use
 # you'll likely need to change it to use
 # valid python version or paths to manage.py
 # valid python version or paths to manage.py
 
 
-15 0 * * * python manage.py prunecategories
-25 0 * * * python manage.py buildactivepostersranking
-25 0 * * * python manage.py clearattachments
-25 0 * * * python manage.py clearreadtracker
-25 0 * * * python manage.py clearsessions
-25 0 * * * python manage.py clearsocial
-25 0 * * * python manage.py invalidatebans
-0 2 * * * python manage.py removeoldips
 0 1 * * * python manage.py deletemarkedusers
 0 1 * * * python manage.py deletemarkedusers
-25 1 * * * python manage.py deleteinactiveusers
-0 2 * * * python manage.py expireuserdatadownloads
+5 1 * * * python manage.py prunecategories
+10 1 * * * python manage.py buildactivepostersranking
+15 1 * * * python manage.py clearattachments
+20 1 * * * python manage.py clearreadtracker
+25 1 * * * python manage.py clearsessions
+30 1 * * * python manage.py clearsocial
+35 1 * * * python manage.py invalidatebans
+40 1 * * * python manage.py removeoldips
+45 1 * * * python manage.py deleteinactiveusers
+50 1 * * * python manage.py expireuserdatadownloads
 0 7 * * * python manage.py prepareuserdatadownloads
 0 7 * * * python manage.py prepareuserdatadownloads

+ 4 - 11
misago/templates/misago/admin/index.html

@@ -56,7 +56,6 @@
           <tr>
           <tr>
             <th colspan="2">
             <th colspan="2">
               <h4>
               <h4>
-                <span class="fa fa-github"></span>
                 {% trans "Misago version" %}
                 {% trans "Misago version" %}
               </h4>
               </h4>
             </th>
             </th>
@@ -65,17 +64,16 @@
         <tbody>
         <tbody>
           <tr>
           <tr>
             <td class="text-center">
             <td class="text-center">
-              {% if allow_version_check %}
-                {% if version_check %}
+              {% if version_check %}
                 <p class="lead text-{% if version_check.is_error %}danger{% else %}success{% endif %}">
                 <p class="lead text-{% if version_check.is_error %}danger{% else %}success{% endif %}">
                   {% if version_check.is_error %}
                   {% if version_check.is_error %}
-                  <span class="fa fa-times fa-lg fa-fw"></span>
+                    <span class="fa fa-times fa-lg fa-fw"></span>
                   {% else %}
                   {% else %}
-                  <span class="fa fa-check fa-lg fa-fw"></span>
+                    <span class="fa fa-check fa-lg fa-fw"></span>
                   {% endif %}
                   {% endif %}
                   {{ version_check.message }}
                   {{ version_check.message }}
                 </p>
                 </p>
-                {% else %}
+              {% else %}
                 <form method="POST">
                 <form method="POST">
                   {% csrf_token %}
                   {% csrf_token %}
                   <button type="submit" class="btn btn-default">
                   <button type="submit" class="btn btn-default">
@@ -83,11 +81,6 @@
                     <span class="name">{% trans "Check version" %}</span>
                     <span class="name">{% trans "Check version" %}</span>
                   </button>
                   </button>
                 </form>
                 </form>
-                {% endif %}
-              {% else %}
-                <p class="lead">
-                  {% trans 'This feature requires "packaging" python module.' %}
-                </p>
               {% endif %}
               {% endif %}
             </td>
             </td>
           </tr>
           </tr>

+ 14 - 8
misago/users/activepostersranking.py

@@ -1,11 +1,12 @@
 from datetime import timedelta
 from datetime import timedelta
 
 
 from django.contrib.auth import get_user_model
 from django.contrib.auth import get_user_model
-from django.db.models import Count
+from django.db.models import Count, Q
 from django.utils import timezone
 from django.utils import timezone
 
 
 from misago.categories.models import Category
 from misago.categories.models import Category
 from misago.conf import settings
 from misago.conf import settings
+from misago.threads.models import Post
 
 
 from .models import ActivityRanking
 from .models import ActivityRanking
 
 
@@ -37,11 +38,16 @@ def build_active_posters_ranking():
     for category in Category.objects.all_categories():
     for category in Category.objects.all_categories():
         ranked_categories.append(category.pk)
         ranked_categories.append(category.pk)
 
 
-    queryset = UserModel.objects.filter(
-        is_active=True, posts__gt=0
-    ).filter(
-        post__posted_on__gte=tracked_since, post__category__in=ranked_categories
-    ).annotate(score=Count('post'))
+    ranked_posts = Q(posted_on__gte=tracked_since) & Q(category__in=ranked_categories)
 
 
-    for ranking in queryset[:settings.MISAGO_RANKING_SIZE].iterator():
-        ActivityRanking.objects.create(user=ranking, score=ranking.score)
+    queryset = (
+        UserModel.objects
+        .annotate(score=Count('post', filter=ranked_posts))
+        .filter(is_active=True, score__gt=0)
+        .order_by('-score')
+    )[:settings.MISAGO_RANKING_SIZE]
+
+    new_ranking = []
+    for ranking in queryset.iterator():
+        new_ranking.append(ActivityRanking(user=ranking, score=ranking.score))
+    ActivityRanking.objects.bulk_create(new_ranking)    

+ 7 - 2
misago/users/management/commands/buildactivepostersranking.py

@@ -1,3 +1,4 @@
+from time import time
 from django.core.management.base import BaseCommand
 from django.core.management.base import BaseCommand
 
 
 from misago.users.activepostersranking import build_active_posters_ranking
 from misago.users.activepostersranking import build_active_posters_ranking
@@ -7,6 +8,10 @@ class Command(BaseCommand):
     help = 'Builds active posters ranking'
     help = 'Builds active posters ranking'
 
 
     def handle(self, *args, **options):
     def handle(self, *args, **options):
-        self.stdout.write("\n\nBuilding active posters ranking...")
+        self.stdout.write("\nBuilding active posters ranking...")
+
+        start_time = time()
         build_active_posters_ranking()
         build_active_posters_ranking()
-        self.stdout.write("Done!")
+        end_time = time() - start_time
+
+        self.stdout.write("Finished after %.2fs" % end_time)

+ 26 - 20
misago/users/management/commands/createsuperuser.py

@@ -37,16 +37,17 @@ class Command(BaseCommand):
             '--email',
             '--email',
             dest='email',
             dest='email',
             default=None,
             default=None,
-            help="Specifies the username for the superuser.",
+            help="Specifies the e-mail for the superuser.",
         )
         )
         parser.add_argument(
         parser.add_argument(
             '--password',
             '--password',
             dest='password',
             dest='password',
             default=None,
             default=None,
-            help="Specifies the username for the superuser.",
+            help="Specifies the password for the superuser.",
         )
         )
         parser.add_argument(
         parser.add_argument(
             '--noinput',
             '--noinput',
+            '--no-input',
             action='store_false',
             action='store_false',
             dest='interactive',
             dest='interactive',
             default=True,
             default=True,
@@ -84,7 +85,7 @@ class Command(BaseCommand):
                 username = username.strip()
                 username = username.strip()
                 validate_username(username)
                 validate_username(username)
             except ValidationError as e:
             except ValidationError as e:
-                self.stderr.write(e.messages[0])
+                self.stderr.write(u'\n'.join(e.messages))
                 username = None
                 username = None
 
 
         if email is not None:
         if email is not None:
@@ -92,16 +93,13 @@ class Command(BaseCommand):
                 email = email.strip()
                 email = email.strip()
                 validate_email(email)
                 validate_email(email)
             except ValidationError as e:
             except ValidationError as e:
-                self.stderr.write(e.messages[0])
+                self.stderr.write(u'\n'.join(e.messages))
                 email = None
                 email = None
 
 
         if password is not None:
         if password is not None:
-            try:
-                password = password.strip()
-                validate_password(password)
-            except ValidationError as e:
-                self.stderr.write(e.messages[0])
-                password = None
+            password = password.strip()
+            if password == '':
+                self.stderr.write("Error: Blank passwords aren't allowed.")
 
 
         if not interactive:
         if not interactive:
             if username and email and password:
             if username and email and password:
@@ -122,29 +120,37 @@ class Command(BaseCommand):
                         validate_username(raw_value)
                         validate_username(raw_value)
                         username = raw_value
                         username = raw_value
                     except ValidationError as e:
                     except ValidationError as e:
-                        self.stderr.write(e.messages[0])
+                        self.stderr.write(u'\n'.join(e.messages))
 
 
                 while not email:
                 while not email:
                     try:
                     try:
-                        raw_value = input("Enter E-mail address: ").strip()
+                        raw_value = input("Enter e-mail address: ").strip()
                         validate_email(raw_value)
                         validate_email(raw_value)
                         email = raw_value
                         email = raw_value
                     except ValidationError as e:
                     except ValidationError as e:
-                        self.stderr.write(e.messages[0])
+                        self.stderr.write(u'\n'.join(e.messages))
 
 
                 while not password:
                 while not password:
+                    raw_value = getpass("Enter password: ")
+                    password_repeat = getpass("Repeat password:")
+                    if raw_value != password_repeat:
+                        self.stderr.write("Error: Your passwords didn't match.")
+                        # Don't validate passwords that don't match.
+                        continue
+                    if raw_value.strip() == '':
+                        self.stderr.write("Error: Blank passwords aren't allowed.")
+                        # Don't validate blank passwords.
+                        continue
                     try:
                     try:
-                        raw_value = getpass("Enter password: ").strip()
                         validate_password(
                         validate_password(
                             raw_value, user=UserModel(username=username, email=email)
                             raw_value, user=UserModel(username=username, email=email)
                         )
                         )
-
-                        repeat_raw_value = getpass("Repeat password: ").strip()
-                        if raw_value != repeat_raw_value:
-                            raise ValidationError("Entered passwords are different.")
-                        password = raw_value
                     except ValidationError as e:
                     except ValidationError as e:
-                        self.stderr.write(e.messages[0])
+                        self.stderr.write(u'\n'.join(e.messages))
+                        response = input('Bypass password validation and create user anyway? [y/N]: ')
+                        if response.lower() != 'y':
+                            continue
+                    password = raw_value
 
 
                 # Call User manager's create_superuser using our wrapper
                 # Call User manager's create_superuser using our wrapper
                 self.create_superuser(username, email, password, verbosity)
                 self.create_superuser(username, email, password, verbosity)

+ 0 - 4
misago/users/models/user.py

@@ -67,10 +67,6 @@ class UserManager(BaseUserManager):
 
 
         validate_username(username)
         validate_username(username)
         validate_email(email)
         validate_email(email)
-        
-        if password:
-            # password is conditional: users created with social-auth don't have one
-            validate_password(password, user=user)
 
 
         if not 'rank' in extra_fields:
         if not 'rank' in extra_fields:
             user.rank = Rank.objects.get_default()
             user.rank = Rank.objects.get_default()

+ 0 - 6
misago/users/tests/test_activepostersranking.py

@@ -38,9 +38,6 @@ class TestActivePostersRanking(AuthenticatedUserTestCase):
         # other user
         # other user
         other_user = UserModel.objects.create_user("OtherUser", "other@user.com", "pass123")
         other_user = UserModel.objects.create_user("OtherUser", "other@user.com", "pass123")
 
 
-        other_user.posts = 1
-        other_user.save()
-
         post_thread(self.category, poster=other_user)
         post_thread(self.category, poster=other_user)
 
 
         build_active_posters_ranking()
         build_active_posters_ranking()
@@ -53,9 +50,6 @@ class TestActivePostersRanking(AuthenticatedUserTestCase):
         post_thread(self.category, poster=self.user)
         post_thread(self.category, poster=self.user)
         post_thread(self.category, poster=self.user)
         post_thread(self.category, poster=self.user)
 
 
-        self.user.posts = 2
-        self.user.save()
-
         build_active_posters_ranking()
         build_active_posters_ranking()
         ranking = get_active_posters_ranking()
         ranking = get_active_posters_ranking()
 
 

+ 1 - 1
misago/users/tests/test_createsuperuser.py

@@ -8,7 +8,7 @@ UserModel = get_user_model()
 
 
 
 
 class CreateSuperuserTests(TestCase):
 class CreateSuperuserTests(TestCase):
-    def test_create_superuser(self):
+    def test_valid_input_creates_superuser(self):
         """command creates superuser"""
         """command creates superuser"""
         out = StringIO()
         out = StringIO()
 
 

+ 13 - 0
misago/users/tests/test_user_create_api.py

@@ -224,6 +224,19 @@ class UserCreateTests(UserTestCase):
             'email': ["You can't register account like this."],
             'email': ["You can't register account like this."],
         })
         })
 
 
+    def test_registration_requires_password(self):
+        """api uses django's validate_password to validate registrations"""
+        response = self.client.post(
+            self.api_link,
+            data={
+                'username': 'Bob',
+                'email': 'loremipsum@dolor.met',
+                'password': '',
+            },
+        )
+        
+        self.assertContains(response, "This field is required", status_code=400)
+
     def test_registration_validates_password(self):
     def test_registration_validates_password(self):
         """api uses django's validate_password to validate registrations"""
         """api uses django's validate_password to validate registrations"""
         response = self.client.post(
         response = self.client.post(

+ 0 - 3
pyclean

@@ -1,3 +0,0 @@
-#!/bin/bash
-
-find ./misago -name '*.pyc' -delete -not -path './misago/frontend/*'

+ 0 - 38
pycodestyle.py

@@ -1,38 +0,0 @@
-"""
-Code style cleanups done after yapf
-"""
-import argparse
-import codecs
-import os
-
-from extras import fixdictsformatting
-
-
-CLEANUPS = [
-    fixdictsformatting,
-]
-
-
-def walk_directory(root, dirs, files):
-    for filename in files:
-        if filename.lower().endswith('.py'):
-            with codecs.open(os.path.join(root, filename), 'r', 'utf-8') as f:
-                filesource = f.read()
-
-            org_source = filesource
-
-            for cleanup in CLEANUPS:
-                filesource = cleanup.fix_formatting(filesource)
-
-            if org_source != filesource:
-                print 'afterclean: %s' % os.path.join(root, filename)
-                with codecs.open(os.path.join(root, filename), 'w', 'utf-8') as f:
-                    f.write(filesource)
-
-
-if __name__ == '__main__':
-    parser = argparse.ArgumentParser()
-    parser.add_argument('path', nargs='?', default='./')
-
-    for args in os.walk(parser.parse_args().path):
-        walk_directory(*args)

+ 18 - 0
requirements.in

@@ -0,0 +1,18 @@
+beautifulsoup4<4.7
+bleach<2.2
+django<2
+djangorestframework<3.7
+django-debug-toolbar<1.9
+django-crispy-forms<1.7
+django-htmlmin<0.11
+django-mptt<0.9
+Faker<0.9
+html5lib==0.999999999
+markdown<2.7
+misago-social-auth-app-django
+path.py<10.4
+pillow<4.2
+psycopg2-binary<2.8
+pytz
+requests<3
+unidecode<1

+ 38 - 18
requirements.txt

@@ -1,19 +1,39 @@
-django~=1.11.1
-djangorestframework~=3.6.3
-beautifulsoup4~=4.6.0
-bleach~=2.0.0
-django-debug-toolbar~=1.8
-django-crispy-forms~=1.6.1
-django-htmlmin~=0.10.0
-django-mptt~=0.8.7
-Faker~=0.8.11
+#
+# This file is autogenerated by pip-compile
+# To update, run:
+#
+#    pip-compile --output-file requirements.txt requirements.in
+#
+beautifulsoup4==4.6.3
+bleach==2.1.4
+certifi==2018.8.24        # via requests
+chardet==3.0.4            # via requests
+django-crispy-forms==1.6.1
+django-debug-toolbar==1.8
+django-htmlmin==0.10.0
+django-mptt==0.8.7
+django==1.11.15
+djangorestframework==3.6.4
+faker==0.8.18
 html5lib==0.999999999
 html5lib==0.999999999
-markdown~=2.6.8
-path.py~=10.3.1
-pillow~=4.1.1
-psycopg2-binary~=2.7.1
-pytz
-pyyaml~=3.13
-requests<3
-misago-social-auth-app-django~=2.1.0
-unidecode~=0.4.20
+idna==2.7                 # via requests
+ipaddress==1.0.22         # via faker
+markdown==2.6.11
+misago-social-auth-app-django==2.1.0
+oauthlib==2.1.0           # via requests-oauthlib, social-auth-core
+olefile==0.45.1           # via pillow
+path.py==10.3.1
+pillow==4.1.1
+psycopg2-binary==2.7.5
+pyjwt==1.6.4              # via social-auth-core
+python-dateutil==2.7.3    # via faker
+pytz==2018.5
+requests-oauthlib==1.0.0  # via social-auth-core
+requests==2.19.1
+six==1.11.0               # via bleach, faker, html5lib, misago-social-auth-app-django, python-dateutil, social-auth-core
+social-auth-core==1.7.0   # via misago-social-auth-app-django
+sqlparse==0.2.4           # via django-debug-toolbar
+text-unidecode==1.2       # via faker
+unidecode==0.4.21
+urllib3==1.23             # via requests
+webencodings==0.5.1       # via html5lib

+ 5 - 7
setup.py

@@ -8,21 +8,19 @@ from misago import __version__ as version
 
 
 SETUP_DIR = os.path.dirname(__file__)
 SETUP_DIR = os.path.dirname(__file__)
 
 
-README = open(os.path.join(SETUP_DIR, 'README.rst'), 'rb').read().decode('utf-8')
+with open(os.path.join(SETUP_DIR, 'README.rst'), 'rb') as f:
+    README = f.read().decode('utf-8')
 
 
 with open(os.path.join(SETUP_DIR, 'requirements.txt'), "r") as f:
 with open(os.path.join(SETUP_DIR, 'requirements.txt'), "r") as f:
-    REQUIREMENTS = [x.strip() for x in f.readlines()]
-
-
-# allow setup.py to be run from any path
-os.chdir(os.path.normpath(os.path.join(os.path.abspath(__file__), os.pardir)))
-
+    REQUIREMENTS = f.read()
 
 
 EXCLUDE_FROM_PACKAGES = [
 EXCLUDE_FROM_PACKAGES = [
     'misago.project_template',
     'misago.project_template',
     'misago.bin'
     'misago.bin'
 ]
 ]
 
 
+# allow setup.py to be run from any path
+os.chdir(os.path.normpath(os.path.join(os.path.abspath(__file__), os.pardir)))
 
 
 setup(
 setup(
     name='Misago',
     name='Misago',

+ 0 - 2
upload

@@ -1,2 +0,0 @@
-#!/bin/sh
-python setup.py sdist upload