diff --git a/.gitignore b/.gitignore
index 11614af2870733183efe883810764d8708bddf8f..cabeafb73e333236e9deb83501b3a24bb0bfc5e6 100644
--- a/.gitignore
+++ b/.gitignore
@@ -113,3 +113,13 @@ dmypy.json
 
 # Pyre type checker
 .pyre/
+Pipfile*
+django_config/
+manage.py
+
+# vim
+*swp
+
+# reports for SonarQube 
+xunit-reports/
+coverage-reports/
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
new file mode 100644
index 0000000000000000000000000000000000000000..f90a08c5d0ce41575f4383ba3cccaf8462834c08
--- /dev/null
+++ b/.gitlab-ci.yml
@@ -0,0 +1,42 @@
+
+image:
+  name: python:3.5
+
+variables:
+  SONAR_TOKEN: "5b1ac2b019175e23aeb4b5618e50893d0db78f1f"
+  SONAR_PROJECTKEY: "ideo-bfc/idgo_ows_accounts_manager"
+  SONAR_HOST_URL: "https://sonarqube.neogeo.fr"
+  GIT_DEPTH: 0
+
+stages:
+  - test1
+  - test2
+
+test_unit:
+  stage: test1
+  before_script:
+    - pip install -r requirements.txt
+    - pip install 'django<2.0.0'
+    - pip install pytest pytest-django pytest-cov
+  script:
+    - pytest --junitxml=xunit-reports/xunit-result-pytest.xml --cov-report xml:coverage-reports/coverage-pytest.xml --cov idgo_ows_account_manager
+  artifacts:
+    paths:
+    - coverage-reports/
+    - xunit-reports/
+    expire_in: 1 week
+
+
+sonarqube-check:
+  image:
+    name: sonarsource/sonar-scanner-cli:latest
+    entrypoint: [""]
+  stage: test2
+  script:
+    - sonar-scanner -Dsonar.qualitygate.wait=true -Dsonar.projectKey=$CI_PROJECT_NAME -Dsonar.projectName=$CI_PROJECT_NAME -Dsonar.projectVersion=$CI_COMMIT_BRANCH
+  allow_failure: true
+  dependencies:
+    - test_unit
+  only:
+    - develop
+    - master
diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
index 0000000000000000000000000000000000000000..e18d6573e499e87f8d01c53f28c3c889e0fd63f2
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,20 @@
+# CHANGELOG
+Tous les changements notables apportés à ce projet seront documentés dans ce dossier.
+
+---
+
+## TABLE DES MATIÈRES
+ - [Reste à réaliser](#Reste-à-réaliser)
+ - [Versions](#Versions)
+   - [[0.1.0] - Version initiale](#[0.1.0]---Version-initiale)
+
+---
+
+## Reste à réaliser
+
+## Versions
+
+### [0.1.0] - Version initiale
+
+* Ajout script OGC compatible tokens
+* Ajout d'une option de configuration dans le profile utilisateur pour créer un token personnalisé.
diff --git a/README.md b/README.md
index 42e7faa97b9b5adbf5cf042d3eec5a3df9b6e9d9..f13b80da6e52f38657dc3a40a413119c88ebd1db 100644
--- a/README.md
+++ b/README.md
@@ -1,3 +1,87 @@
 # IDGO WMS Token
 
-Gestion des token pour l'accès à Mapserver en utilisant le protocole WMS 
\ No newline at end of file
+Gestion des token pour l'accès à Mapserver en utilisant le protocole WMS
+
+---
+
+## TABLE DES MATIÈRES
+ - [TABLE DES MATIÈRES](#TABLE-DES-MATIÈRES)
+ - [Installation](#Installation)
+ - [Configuration](#Configuration)
+ - [Utilisation](#Utilisation)
+   - [Démarrage des dockers](#Démarrage-des-dockers)
+   - [Arrêt des dockers](#Arrêt-des-dockers)
+ - [Versions](#Versions)
+ - [Auteurs](#Auteurs)
+
+---
+
+## Installation
+
+C'est un plugin pour idgo, donc installer idgo avant !
+
+```
+pip install git+https://@gitlab.neogeo.fr/ideo-bfc/idgo_ows_accounts_manager.git@master#egg=idgo_ows_accounts_manager
+```
+
+## Configuration
+
+Pour activer l'application django, et activer le `context_processor` chargeant tous les templates de modification de profil utilisateur :
+Dans le settings.py:
+
+```
+ACCOUNT_MANAGER_APPS = [
+    'idgo_ows_account_manager',
+]
+
+INSTALLED_APPS = [
+[...]
+] + RESOURCE_APPS + ACCOUNT_MANAGER_APPS
+
+
+TEMPLATES = [
+    {
+        'BACKEND': 'django.template.backends.django.DjangoTemplates',
+        'DIRS': [],
+        'APP_DIRS': True,
+        'OPTIONS': {
+            'context_processors': [
+[...]
+                'idgo_ows_account_manager.context_processors.list_account_manager_extensions',
+            ],
+        },
+    },
+]
+
+DISABLE_ACCOUNT_PASSWORD = True
+```
+
+`DISABLE_ACCOUNT_PASSWORD` permet de ne pas afficher le mot de passe utilisateur
+
+Pour charger les 3 vues (pour creer, supprimer, changer le mot de passe) dans urls.py:
+```
+for app in settings.RESOURCE_APPS + settings.ACCOUNT_MANAGER_APPS:
+    # on insert apres idgo_admin
+    urlpatterns.insert(2, url(r'^', include('{}.urls'.format(app), namespace=app)),)
+```
+
+Pour que le script OGC utilise en basic auth le username en nom d'utilisateur et le token en mot de passe.
+
+
+
+## Utilisation
+
+### Configurer son token
+
+Dans la nouvelle section "Compte WMS" du profil utilisateur, choisir "Créer un compte WMS", le login / mot de passe apparaitra, ainsi que les boutons pour supprimer le compte et regéner le mot de passe.
+
+### Utiliser son token
+
+Dans QGis, rensignier le mot de passe et le mot de passe dans la connexion WMS/WMTS/WFS, l'URL à utiliser est https://[qualif-]/[preprod-]/[]ogc.ternum-bfc.fr/
+
+## Versions
+
+Voir le fichier [CHANGELOG](CHANGELOG.md)
+
+## Auteurs
+ * Néogéo Technologies
diff --git a/idgo_admin_mock/__init__.py b/idgo_admin_mock/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/idgo_admin_mock/admin.py b/idgo_admin_mock/admin.py
new file mode 100644
index 0000000000000000000000000000000000000000..8c38f3f3dad51e4585f3984282c2a4bec5349c1e
--- /dev/null
+++ b/idgo_admin_mock/admin.py
@@ -0,0 +1,3 @@
+from django.contrib import admin
+
+# Register your models here.
diff --git a/idgo_admin_mock/apps.py b/idgo_admin_mock/apps.py
new file mode 100644
index 0000000000000000000000000000000000000000..62d5b2c84c38f124fba4a85d60b7389c539093ff
--- /dev/null
+++ b/idgo_admin_mock/apps.py
@@ -0,0 +1,5 @@
+from django.apps import AppConfig
+
+
+class IdgoAdminMockConfig(AppConfig):
+    name = 'idgo_admin_mock'
diff --git a/idgo_admin_mock/migrations/__init__.py b/idgo_admin_mock/migrations/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/idgo_admin_mock/models.py b/idgo_admin_mock/models.py
new file mode 100644
index 0000000000000000000000000000000000000000..71a836239075aa6e6e4ecb700e9c42c95c022d91
--- /dev/null
+++ b/idgo_admin_mock/models.py
@@ -0,0 +1,3 @@
+from django.db import models
+
+# Create your models here.
diff --git a/idgo_admin_mock/tests.py b/idgo_admin_mock/tests.py
new file mode 100644
index 0000000000000000000000000000000000000000..7ce503c2dd97ba78597f6ff6e4393132753573f6
--- /dev/null
+++ b/idgo_admin_mock/tests.py
@@ -0,0 +1,3 @@
+from django.test import TestCase
+
+# Create your tests here.
diff --git a/idgo_admin_mock/urls.py b/idgo_admin_mock/urls.py
new file mode 100644
index 0000000000000000000000000000000000000000..967d94c422543b91fe4f5b4439450166fecd1516
--- /dev/null
+++ b/idgo_admin_mock/urls.py
@@ -0,0 +1,6 @@
+from django.conf.urls import url
+from idgo_admin_mock import views
+
+urlpatterns = [
+    url(r'^fake/', views.mock_view, name="update_account"),
+]
diff --git a/idgo_admin_mock/views.py b/idgo_admin_mock/views.py
new file mode 100644
index 0000000000000000000000000000000000000000..968bc5580b330174fce0368d17f2e380675dd835
--- /dev/null
+++ b/idgo_admin_mock/views.py
@@ -0,0 +1,4 @@
+from django.http import HttpResponse
+
+def mock_view(request):
+    return HttpResponse("OK")
diff --git a/idgo_ows_account_manager/__init__.py b/idgo_ows_account_manager/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/idgo_ows_account_manager/admin.py b/idgo_ows_account_manager/admin.py
new file mode 100644
index 0000000000000000000000000000000000000000..5a9455e253010fd84a329a08b5484c9c09247ea4
--- /dev/null
+++ b/idgo_ows_account_manager/admin.py
@@ -0,0 +1,5 @@
+from django.contrib import admin
+from idgo_ows_account_manager import models
+
+# Register your models here.
+admin.site.register(models.WMSToken)
diff --git a/idgo_ows_account_manager/apps.py b/idgo_ows_account_manager/apps.py
new file mode 100644
index 0000000000000000000000000000000000000000..78b8b6e762d704188632d46be7ea9ec2acd3a7dc
--- /dev/null
+++ b/idgo_ows_account_manager/apps.py
@@ -0,0 +1,5 @@
+from django.apps import AppConfig
+
+
+class IdgoOwsAccountManagerConfig(AppConfig):
+    name = 'idgo_ows_account_manager'
diff --git a/idgo_ows_account_manager/auth_ogc.py b/idgo_ows_account_manager/auth_ogc.py
new file mode 100755
index 0000000000000000000000000000000000000000..d9c6a91141fabf78292c6a35f9fbdf6c2c740923
--- /dev/null
+++ b/idgo_ows_account_manager/auth_ogc.py
@@ -0,0 +1,189 @@
+#!/WEBS/ternum/idgo.ternum.fr/docs/neogeo-idgo/bin/python3
+# Copyright (c) 2017-2020 Neogeo-Technologies.
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+
+import logging
+import os
+import sys
+from urllib.parse import parse_qs
+from urllib.parse import urlparse
+python_home = "/WEBS/ternum/idgo.ternum.fr/docs/neogeo-idgo"
+sys.path.append(python_home)
+
+# Obliqé pour WSGIAuthUserScript
+activate_this = python_home + '/bin/activate_this.py'
+with open(activate_this) as venv:
+    exec(venv.read(), {'__file__': activate_this})
+
+from wl_proxy import load_proxy_environment
+load_proxy_environment()
+os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'django_config.settings')
+import django  # noqa: E402
+django.setup()
+from django.contrib.auth.models import User  # noqa: E402
+from django.db.models import Q  # noqa: E402
+from functools import reduce  # noqa: E402
+from idgo_admin.models import Dataset  # noqa: E402
+from idgo_admin.models import Organisation   # noqa: E402
+from idgo_admin.models import Resource  # noqa: E402
+from operator import ior  # noqa: E402
+
+from django.conf import settings
+
+if settings.DEBUG:
+    AUTH_SCRIPT_LOGFILE = '/FILER/idgo.ternum.fr/media/logos/auth-ogc.ternum.fr_error.log'
+    OTHER_LOGFILE = '/FILER/idgo.ternum.fr/media/logos/default.ternum.fr_error.log'
+
+    logger = logging.getLogger('')
+    stream_handler = logging.FileHandler(OTHER_LOGFILE)
+    logger.addHandler(stream_handler)
+    logger.setLevel(logging.DEBUG)
+
+    logger = logging.getLogger('auth_ogc')
+    stream_handler = logging.FileHandler(AUTH_SCRIPT_LOGFILE)
+    logger.addHandler(stream_handler)
+    logger.setLevel(logging.DEBUG)
+
+    try:
+        if os.path.exists(OTHER_LOGFILE):
+            os.chmod(OTHER_LOGFILE, 0o666)
+
+        if os.path.exists(AUTH_SCRIPT_LOGFILE):
+            os.chmod(AUTH_SCRIPT_LOGFILE, 0o666)
+    except:
+        pass
+else:
+    logger = logging.getLogger('auth_ogc')
+    logger.setLevel(logging.WARN)
+
+AUTHORIZED_PREFIX = ['/maps/', '/wfs/', '/wms/', '/wxs/']
+# used for parsing address when basic auth is provided
+PRIVATE_AUTHORIZED_PREFIX = ["/private{prefix}".format(prefix=p)
+                             for p in AUTHORIZED_PREFIX]
+
+
+def retrieve_resources_through_ows_url(url):
+    parsed_url = urlparse(url.lower())
+    qs = parse_qs(parsed_url.query)
+    if 'layers' in qs:
+        layers = qs.get('layers')[-1]
+    elif 'typename' in qs:
+        layers = qs.get('typename')[-1]
+    elif 'typenames' in qs:
+        layers = qs.get('typenames')[-1]
+    else:
+        layers = None
+    if not layers:
+        return None
+    layers = set(layers.replace(' ', '').split(','))
+    layers = [layer.split(':')[-1] for layer in layers]
+    datasets_filters = [
+        Q(slug__in=layers),
+        Q(organisation__in=Organisation.objects.filter(slug__in=layers).distinct()),
+        ]
+    datasets = Dataset.objects.filter(reduce(ior, datasets_filters)).distinct()
+    resources_filters = [
+        Q(dataset__in=datasets),
+        Q(layer__name__in=layers),
+        ]
+    resources = Resource.objects.filter(reduce(ior, resources_filters)).distinct()
+    return resources
+
+
+def check_password(environ, user, password):
+
+    url = environ['REQUEST_URI']
+
+    logger.debug('Checking user %s rights to url %s', user, url)
+
+    # check path is authorized
+
+    is_path_authorized = False
+    for prefix in AUTHORIZED_PREFIX + PRIVATE_AUTHORIZED_PREFIX:
+        if url.startswith(prefix):
+            is_path_authorized = True
+
+    if not is_path_authorized:
+        logger.error("path '%s' is unauthorized", url)
+        return False
+
+    # Get Capabilities and metadata are always athorized
+    qs = parse_qs(urlparse(url.lower()).query)
+
+    request = qs.get('request')
+    logger.debug(qs)
+    public_requests = [
+        "getcapabilities",
+        "getmetadata",
+        "getlegendgraphic",
+        "describefeaturetype",
+        "describelayer",
+        "getstyles",
+        ]
+
+    if request[-1] in public_requests:
+        logger.debug("URL request is public")
+        return True
+
+    try:
+        user = User.objects.get(username=user, is_active=True)
+    except User.DoesNotExist:
+        logger.debug("User %s does not exist (or is not active)" % user)
+    else:
+        if str(user.wmstoken.token) != password:
+            logger.error("User %s provided bad password", user)
+            #logger.debug("    '%s' instead of '%s'", password, user.wmstoken.token)
+            return False
+
+    resources = retrieve_resources_through_ows_url(url)
+    if not resources:
+        logger.error("Unable to get resources")
+        return False
+    # Refuse query if one of the resources is not available/authorized
+    for resource in resources:
+        if resource.anonymous_access:
+            continue
+        if not resource.is_profile_authorized(user):
+            logger.error(
+                "Resource '{resource}' is not authorized to user '{user}'.".format(
+                    resource=resource.pk, user=user.username))
+            return False
+    return True
+
+
+if __name__ == '__main__':
+    while True:
+        try:
+            line = sys.stdin.readline().strip()
+            logger.debug("REMAP ogc auth: %s" % line)
+            headers = {"REQUEST_URI": line}
+            # Remove querystring (handled by apache)
+            path = line.split("?")[0]
+
+            # if ressource is accessible by anonymous => public,
+            # otherwise check password (=> private)
+            if check_password(headers, "", ""):
+                response = "http://localhost:8001/public{uri}".format(uri=path)
+            else:
+                response = "http://localhost:8001/private{uri}".format(uri=path)
+
+            logger.debug("response : %s" % response)
+            sys.stdout.write(response + '\n')
+            sys.stdout.flush()
+        except Exception as e:
+            logger.error(e)
+            sys.stdout.write('NULL\n')
+            sys.stdout.flush()
diff --git a/idgo_ows_account_manager/context_processors.py b/idgo_ows_account_manager/context_processors.py
new file mode 100644
index 0000000000000000000000000000000000000000..bf23c769fe52b211688242447fa29531ca93bcb7
--- /dev/null
+++ b/idgo_ows_account_manager/context_processors.py
@@ -0,0 +1,33 @@
+from django.conf import settings
+
+
+def list_account_manager_extensions(request):
+    """
+    En considerant une liste regroupant les extensions de compte utilisateur:
+    ACCOUNT_MANAGER_APPS = ['idgo_ows_account_manager', ...]
+    INSTALLED_APPS = CORE_APPS + ... + ACCOUNT_MANAGER_APPS
+
+    A charger dans les settings:
+    TEMPLATES = [
+        {
+            # ...
+            'OPTIONS': {
+                'context_processors': [
+                    # ...
+                    'idgo_ows_account_manager.context_processors.list_account_manager_extensions'
+                ],
+            },
+        },
+    ]
+    Permet d'avoir la variable 'ACCOUNT_MANAGER_APPS' accessible depuis tous les
+    templates.
+    """
+
+    return {
+        'ACCOUNT_MANAGER_APPS': [
+            {
+                'name': app,
+                'template': '{}/account_manager_extent.html'.format(app)
+            } for app in getattr(settings, 'ACCOUNT_MANAGER_APPS', [])
+        ]
+    }
diff --git a/idgo_ows_account_manager/migrations/0001_initial.py b/idgo_ows_account_manager/migrations/0001_initial.py
new file mode 100644
index 0000000000000000000000000000000000000000..88d30e2f53af5e69534014bf5d39d5b3fb2ac1bd
--- /dev/null
+++ b/idgo_ows_account_manager/migrations/0001_initial.py
@@ -0,0 +1,26 @@
+# Generated by Django 3.0 on 2020-03-12 15:45
+
+from django.conf import settings
+from django.db import migrations, models
+import django.db.models.deletion
+import uuid
+
+
+class Migration(migrations.Migration):
+
+    initial = True
+
+    dependencies = [
+        migrations.swappable_dependency(settings.AUTH_USER_MODEL),
+    ]
+
+    operations = [
+        migrations.CreateModel(
+            name='WMSToken',
+            fields=[
+                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+                ('token', models.UUIDField(default=uuid.uuid4)),
+                ('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
+            ],
+        ),
+    ]
diff --git a/idgo_ows_account_manager/migrations/__init__.py b/idgo_ows_account_manager/migrations/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/idgo_ows_account_manager/models.py b/idgo_ows_account_manager/models.py
new file mode 100644
index 0000000000000000000000000000000000000000..1b576a14c2c702239288abad3071ef6bd8e99dff
--- /dev/null
+++ b/idgo_ows_account_manager/models.py
@@ -0,0 +1,14 @@
+import uuid
+
+from django.db import models
+from django.contrib.auth import get_user_model
+
+User = get_user_model()
+
+
+class WMSToken(models.Model):
+    user = models.OneToOneField(User, on_delete=models.CASCADE)
+    token = models.UUIDField(default=uuid.uuid4)
+
+    def __str__(self):
+        return self.user.username
diff --git a/idgo_ows_account_manager/templates/idgo_ows_account_manager/account_manager_extent.html b/idgo_ows_account_manager/templates/idgo_ows_account_manager/account_manager_extent.html
new file mode 100644
index 0000000000000000000000000000000000000000..33449930d35d6f95aa09c66e3bedc170001a2552
--- /dev/null
+++ b/idgo_ows_account_manager/templates/idgo_ows_account_manager/account_manager_extent.html
@@ -0,0 +1,12 @@
+<div class="well">
+    <h4 class="modal-title">Compte WMS</h4>
+    <br />
+{% if not user.wmstoken %}
+    <p>Votre compte WMS n'est pas encore activé.</p>
+    <a class="btn btn-default" href="{% url "idgo_ows_account_manager:create_ows_account" %}">Créer mon compte WMS</a>
+{% else %}
+	<p>Votre compte WMS est activé : <em>compte utilisateur</em> <strong>{{ user.username }}</strong> / <em>mot de passe</em> <strong>{{ user.wmstoken.token }}</strong></p>
+	<a class="btn btn-default" href="{% url "idgo_ows_account_manager:delete_ows_account" %}">Supprimer mon compte WMS</a></li>
+	<a class="btn btn-default" href="{% url "idgo_ows_account_manager:change_ows_password" %}">Générer nouveau mot de passe WMS</a></li>
+{% endif %}
+</div>
diff --git a/idgo_ows_account_manager/tests.py b/idgo_ows_account_manager/tests.py
new file mode 100644
index 0000000000000000000000000000000000000000..36959992f4c3406f5fea8faae73dbc1389e8ae8e
--- /dev/null
+++ b/idgo_ows_account_manager/tests.py
@@ -0,0 +1,79 @@
+from django.contrib.auth import get_user_model
+from django.test import Client
+from django.test import TestCase
+from django.urls import reverse
+
+from idgo_ows_account_manager.models import WMSToken
+from idgo_ows_account_manager.views import create_ows_account
+
+# Create your tests here.
+
+class TestViewsOWSAccount(TestCase):
+
+    def teardown(self):
+        user = get_user_model()
+        WMSToken.objects.filter(user__username="sdarocha").delete()
+        user.objects.filter(username="sdarocha").delete()
+
+
+    def test_create_ows_account(self):
+        """
+        Create a user without token
+        call create_ows_account
+        ensure his token is created
+        """
+        user = get_user_model()
+        u = user.objects.create(username="sdarocha", password="123")
+        u.save()
+
+        assert not WMSToken.objects.filter(user=u)
+
+        c = Client()
+        c.force_login(u)
+        c.get(reverse('idgo_ows_account_manager:create_ows_account'))
+
+        assert WMSToken.objects.get(user=u).token
+
+
+    def test_delete_ows_account(self):
+        """
+        Create a user and his token
+        call delete_ows_account
+        ensure his token has disapeared
+        """
+        user = get_user_model()
+        u = user.objects.create(username="sdarocha", password="123")
+        u.save()
+        assert not WMSToken.objects.filter(user=u)
+
+        WMSToken.objects.create(user=u)
+        assert WMSToken.objects.get(user=u).token
+
+        c = Client()
+        c.force_login(u)
+        c.get(reverse('idgo_ows_account_manager:delete_ows_account'))
+
+        assert not WMSToken.objects.filter(user=u)
+
+
+    def test_change_ows_password(self):
+        """
+        Create a user and his token
+        call change_ows_password
+        ensure token is different
+        """
+        user = get_user_model()
+        u = user.objects.create(username="sdarocha", password="123")
+        u.save()
+        assert not WMSToken.objects.filter(user=u)
+
+        WMSToken.objects.create(user=u)
+        token = WMSToken.objects.get(user=u).token
+        assert token
+
+        c = Client()
+        c.force_login(u)
+        c.get(reverse('idgo_ows_account_manager:change_ows_password'))
+
+        token2 = WMSToken.objects.get(user=u).token
+        assert token != token2
diff --git a/idgo_ows_account_manager/urls.py b/idgo_ows_account_manager/urls.py
new file mode 100644
index 0000000000000000000000000000000000000000..c367cc30574f7a930364d8d99cb7fe2c0e2bbdc0
--- /dev/null
+++ b/idgo_ows_account_manager/urls.py
@@ -0,0 +1,11 @@
+from django.conf.urls import url, include
+
+from idgo_ows_account_manager.views import create_ows_account
+from idgo_ows_account_manager.views import change_ows_password
+from idgo_ows_account_manager.views import delete_ows_account
+
+urlpatterns = [
+    url(r'^ows_account/create/', create_ows_account, name="create_ows_account"),
+    url(r'^ows_account/delete/', delete_ows_account, name="delete_ows_account"),
+    url(r'^ows_account/change/', change_ows_password, name="change_ows_password"),
+]
diff --git a/idgo_ows_account_manager/views.py b/idgo_ows_account_manager/views.py
new file mode 100644
index 0000000000000000000000000000000000000000..8affa836033f614cf09a78a04d3bfe6d1bc03350
--- /dev/null
+++ b/idgo_ows_account_manager/views.py
@@ -0,0 +1,23 @@
+from django.contrib.auth import get_user_model
+from django.shortcuts import redirect
+
+from idgo_ows_account_manager.models import WMSToken
+
+UPDATE_ACCOUNT_PAGE = 'idgo_admin:update_account'
+
+
+def create_ows_account(request):
+    if not hasattr(request.user, "wmstoken"):
+        WMSToken.objects.create(user=request.user)
+    return redirect(UPDATE_ACCOUNT_PAGE)
+
+
+def delete_ows_account(request):
+    WMSToken.objects.filter(user=request.user).delete()
+    return redirect(UPDATE_ACCOUNT_PAGE)
+
+
+def change_ows_password(request):
+    WMSToken.objects.filter(user=request.user).delete()
+    WMSToken.objects.create(user=request.user)
+    return redirect(UPDATE_ACCOUNT_PAGE)
diff --git a/manage.py b/manage.py
new file mode 100755
index 0000000000000000000000000000000000000000..f7e931aae4323418d9071ebb4fa50292d92bf418
--- /dev/null
+++ b/manage.py
@@ -0,0 +1,22 @@
+#!/usr/bin/env python
+import os
+import sys
+
+if __name__ == "__main__":
+    os.environ.setdefault("DJANGO_SETTINGS_MODULE", "test_config.settings")
+    try:
+        from django.core.management import execute_from_command_line
+    except ImportError:
+        # The above import may fail for some other reason. Ensure that the
+        # issue is really that Django is missing to avoid masking other
+        # exceptions on Python 2.
+        try:
+            import django
+        except ImportError:
+            raise ImportError(
+                "Couldn't import Django. Are you sure it's installed and "
+                "available on your PYTHONPATH environment variable? Did you "
+                "forget to activate a virtual environment?"
+            )
+        raise
+    execute_from_command_line(sys.argv)
diff --git a/pytest.ini b/pytest.ini
new file mode 100644
index 0000000000000000000000000000000000000000..5ee4d3346796be74b957ba3c87f58e57061d5a55
--- /dev/null
+++ b/pytest.ini
@@ -0,0 +1,5 @@
+[pytest]
+DJANGO_SETTINGS_MODULE = test_config.settings
+python_files = tests.py test_*.py *_tests.py
+junit_family=xunit1
+
diff --git a/requirements.txt b/requirements.txt
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/setup.py b/setup.py
new file mode 100644
index 0000000000000000000000000000000000000000..cb10be5467887222af28c7e0000643e0916bea9b
--- /dev/null
+++ b/setup.py
@@ -0,0 +1,23 @@
+from setuptools import setup, find_packages
+
+
+def get_requirements():
+    with open('requirements.txt') as req_file:
+        reqs = req_file.readlines()
+    return [req for req in reqs if not req.startswith('-e')]
+
+
+def get_long_description():
+    with open('README.md', 'rb') as desc_file:
+        description = desc_file.read().decode('utf-8')
+    return description
+
+
+setup(
+    name="idgo_ows_account_manager",
+    descrtiption="Plugin de gestion de token WMS",
+    packages=find_packages(),
+    long_description=get_long_description(),
+    url='https://gitlab.neogeo.fr/ideo-bfc/idgo_ows_accounts_manager',
+    install_requires=get_requirements()
+)
diff --git a/sonar-project.properties b/sonar-project.properties
new file mode 100644
index 0000000000000000000000000000000000000000..d30cf6ac6a23c9574ebe9d6ef7363c3aed021855
--- /dev/null
+++ b/sonar-project.properties
@@ -0,0 +1,3 @@
+sonar.sources = idgo_ows_account_manager/
+sonar.exclusions=**/*test*
+sonar.tests = idgo_ows_account_manager/tests.py
diff --git a/test_config/__init__.py b/test_config/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/test_config/settings.py b/test_config/settings.py
new file mode 100644
index 0000000000000000000000000000000000000000..d6a2930a21c83c1def4ad59670acf32c19573f0a
--- /dev/null
+++ b/test_config/settings.py
@@ -0,0 +1,122 @@
+"""
+Django settings for test_config project.
+
+Generated by 'django-admin startproject' using Django 1.11.29.
+
+For more information on this file, see
+https://docs.djangoproject.com/en/1.11/topics/settings/
+
+For the full list of settings and their values, see
+https://docs.djangoproject.com/en/1.11/ref/settings/
+"""
+
+import os
+
+# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
+BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
+
+
+# Quick-start development settings - unsuitable for production
+# See https://docs.djangoproject.com/en/1.11/howto/deployment/checklist/
+
+# SECURITY WARNING: keep the secret key used in production secret!
+SECRET_KEY = 'kv+a0nqwbf(9gr9&&j%6*oc+d_43y493k63=poha*yj49y2g&%'
+
+# SECURITY WARNING: don't run with debug turned on in production!
+DEBUG = True
+
+ALLOWED_HOSTS = []
+
+
+ACCOUNT_MANAGER_APPS = [
+    'idgo_ows_account_manager',
+]
+
+
+# Application definition
+
+INSTALLED_APPS = [
+    'django.contrib.admin',
+    'django.contrib.auth',
+    'django.contrib.contenttypes',
+    'django.contrib.sessions',
+    'django.contrib.messages',
+    'django.contrib.staticfiles',
+] + ACCOUNT_MANAGER_APPS
+
+MIDDLEWARE = [
+    'django.middleware.security.SecurityMiddleware',
+    'django.contrib.sessions.middleware.SessionMiddleware',
+    'django.middleware.common.CommonMiddleware',
+    'django.middleware.csrf.CsrfViewMiddleware',
+    'django.contrib.auth.middleware.AuthenticationMiddleware',
+    'django.contrib.messages.middleware.MessageMiddleware',
+    'django.middleware.clickjacking.XFrameOptionsMiddleware',
+]
+
+ROOT_URLCONF = 'test_config.urls'
+
+TEMPLATES = [
+    {
+        'BACKEND': 'django.template.backends.django.DjangoTemplates',
+        'DIRS': [],
+        'APP_DIRS': True,
+        'OPTIONS': {
+            'context_processors': [
+                'django.template.context_processors.debug',
+                'django.template.context_processors.request',
+                'django.contrib.auth.context_processors.auth',
+                'django.contrib.messages.context_processors.messages',
+                'idgo_ows_account_manager.context_processors.list_account_manager_extensions',
+            ],
+        },
+    },
+]
+
+WSGI_APPLICATION = 'test_config.wsgi.application'
+
+
+# Database
+# https://docs.djangoproject.com/en/1.11/ref/settings/#databases
+
+DATABASES = {
+    'default': {
+        'ENGINE': 'django.db.backends.sqlite3',
+        'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
+    }
+}
+
+
+# Password validation
+# https://docs.djangoproject.com/en/1.11/ref/settings/#auth-password-validators
+
+AUTH_PASSWORD_VALIDATORS = [
+    {
+        'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
+    },
+    {
+        'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
+    },
+    {
+        'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
+    },
+    {
+        'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
+    },
+]
+
+
+# Internationalization
+# https://docs.djangoproject.com/en/1.11/topics/i18n/
+
+LANGUAGE_CODE = 'en-us'
+
+TIME_ZONE = 'UTC'
+
+USE_I18N = True
+
+USE_L10N = True
+
+USE_TZ = True
+
+
diff --git a/test_config/urls.py b/test_config/urls.py
new file mode 100644
index 0000000000000000000000000000000000000000..e75d5d9cc63a24d57d16eda940326641bccece36
--- /dev/null
+++ b/test_config/urls.py
@@ -0,0 +1,26 @@
+"""test_config URL Configuration
+
+The `urlpatterns` list routes URLs to views. For more information please see:
+    https://docs.djangoproject.com/en/1.11/topics/http/urls/
+Examples:
+Function views
+    1. Add an import:  from my_app import views
+    2. Add a URL to urlpatterns:  url(r'^$', views.home, name='home')
+Class-based views
+    1. Add an import:  from other_app.views import Home
+    2. Add a URL to urlpatterns:  url(r'^$', Home.as_view(), name='home')
+Including another URLconf
+    1. Import the include() function: from django.conf.urls import url, include
+    2. Add a URL to urlpatterns:  url(r'^blog/', include('blog.urls'))
+"""
+from django.conf.urls import url, include
+from django.contrib import admin
+from django.conf import settings
+
+urlpatterns = [
+    url(r'^admin/', admin.site.urls),
+    url(r'^', include(('idgo_admin_mock.urls', 'idgo_admin_mock'), namespace='idgo_admin')),
+]
+
+for app in settings.ACCOUNT_MANAGER_APPS:
+    urlpatterns.insert(2, url(r'^', include(('{}.urls'.format(app), app), namespace=app)),)
diff --git a/test_config/wsgi.py b/test_config/wsgi.py
new file mode 100644
index 0000000000000000000000000000000000000000..f237f27c288126febb79350d1d4b647b4e365527
--- /dev/null
+++ b/test_config/wsgi.py
@@ -0,0 +1,16 @@
+"""
+WSGI config for test_config project.
+
+It exposes the WSGI callable as a module-level variable named ``application``.
+
+For more information on this file, see
+https://docs.djangoproject.com/en/1.11/howto/deployment/wsgi/
+"""
+
+import os
+
+from django.core.wsgi import get_wsgi_application
+
+os.environ.setdefault("DJANGO_SETTINGS_MODULE", "test_config.settings")
+
+application = get_wsgi_application()