Parcourir la source

Premier commit

fanch il y a 1 an
Parent
commit
ae22c8cb38

+ 1 - 1
setup.py

@@ -9,7 +9,7 @@ install_requires = [
 ]
 
 tests_require = [
-    "fab",
+    "mkfab",
     "pytest"
 ]
 

+ 31 - 0
src/djangotools/apps.py

@@ -0,0 +1,31 @@
+
+from django.apps import AppConfig
+import datetime
+
+from django.conf import settings
+
+from djangotools.common import iter_module
+
+
+class DjangoToolsAppConfig(AppConfig):
+    name = 'djangotools'
+    _is_ready = False
+
+
+    def _load_context(self):
+        from djangotools.views.context import base
+        base.load_modules()
+
+    def _load_views(self):
+        for module in iter_module(settings.VIEWS_MODULES): pass
+
+    def _init(self):
+        self._load_context()
+        self._load_views()
+
+    def ready(self):
+        if not self._is_ready:
+            self._init()
+        self._is_ready = True
+
+

+ 17 - 0
src/djangotools/cmdline/commands/migrate/__init__.py

@@ -0,0 +1,17 @@
+
+from djangotools.cmdline import Command
+
+from djangotools.run import Migrate
+
+
+class MigrateCommand(Command):
+
+    NAME = "migrate"
+    HELP = "Lance les migrations"
+
+    ARGUMENT = [
+    ]
+
+    def run(self, data):
+        handler = Migrate()
+        handler.handle()

+ 1 - 0
src/djangotools/cmdline/commands/run/__init__.py

@@ -22,6 +22,7 @@ class RunCommand(Command):
         Argument("-e", "--stderr", nargs=1, help="Crée le server dans un autre processus"),
         Argument("-b", "--bind",  help="Adresse d'écoute du serveur exemple localhost:8080"),
         Argument("-C", "--no-cron", action="store_true", help="Ne pas lancer le cron"),
+
     ]
 
     def start_cron(self, hostname):

+ 3 - 1
src/djangotools/cmdline/common/args.py

@@ -12,11 +12,13 @@ class ArgsParser(argparse.ArgumentParser):
 
     def __init__(self, commands_dirs):
         super().__init__()
+        if isinstance(commands_dirs, str): commands_dirs = [commands_dirs]
         self.commands_dirs = commands_dirs
         parser = self.add_subparsers(parser_class=argparse.ArgumentParser)
         self.add_argument("-a", "--app-dir",  help="Dossier de données du serveur")
         self.add_argument("-s", "--settings",  help="Module de settings à utiliser")
-        for cmd in Command.load(commands_dirs):
+        from djangotools.cmdline import commands
+        for cmd in Command.load([commands, *commands_dirs]):
             kwargs = {}
             if cmd.HELP: kwargs["help"] = cmd.HELP
             if cmd.ALIASES: kwargs["aliases"] = cmd.ALIASES

+ 7 - 34
src/djangotools/cmdline/common/command.py

@@ -3,6 +3,8 @@ from pathlib import Path
 
 from django.core.exceptions import ImproperlyConfigured
 
+from djangotools.common.module_loader import iter_module_content
+
 
 class CommandData:
 
@@ -50,44 +52,13 @@ class Command:
         thread.start()
         return thread
 
-    @staticmethod
-    def _load(root_module):
-        ret = {}
-        root = Path(root_module.__file__).parent
-        queue = [root]
-        root_module_str = root_module.__name__
-        assert root_module_str != "__main__"
-        while queue:
-            current = queue.pop()
-            for file in current.iterdir():
-                if file.name == "__pycache__": continue
-                if file.is_dir():
-                    queue.append(file)
-                    continue
-                if file.is_file() and file.name.endswith(".py"):
-                    module_name = root_module_str+"."+str(file.relative_to(root))[:-3].replace("/",".").replace(".py", "").replace("__init__", "")
-                    if module_name[-1] == ".": module_name = module_name[:-1]
-                    module = __import__(module_name, fromlist=['object'])
-                    for x in dir(module):
-                        data = getattr(module, x)
-                        try:
-                            if (isinstance(data, type) and issubclass(data, Command)
-                                    and data != Command and data.NAME is not None):
-                                ret[data.NAME]=data
-                        except ImproperlyConfigured:
-                            pass
-        return ret
 
     @staticmethod
     def load(commands_dirs=None):
-        if isinstance(commands_dirs, str): commands_dirs = [commands_dirs]
-        from djangotools.cmdline import commands
         ret = {}
-        commands_dirs = [commands]+list(commands_dirs or [])
-        for commands_dir in commands_dirs:
-            if isinstance(commands_dir, str):
-                commands_dir = __import__(commands_dir, fromlist=['object'])
-            ret.update(Command._load(commands_dir))
+        for name, val in  iter_module_content(commands_dirs, classe=Command):
+            if val.NAME:
+                ret[val.NAME] = val
 
         return set(ret.values())
 
@@ -95,3 +66,5 @@ class Command:
 
 
 
+
+

+ 2 - 0
src/djangotools/common/__init__.py

@@ -0,0 +1,2 @@
+from djangotools.common.module_loader import iter_module, iter_module_content
+from djangotools.common.models import BaseSerializationModel

+ 15 - 0
src/djangotools/model/tools.py → src/djangotools/common/models.py

@@ -3,6 +3,21 @@ from collections import defaultdict
 from django.apps import apps
 from django.db.models.base import ModelBase, Model
 
+from django.db import models
+
+
+class Manager(models.Manager):
+    def create_from_json(self, **kwargs):
+        return self.create(**kwargs)
+
+
+class BaseSerializationModel:
+    pass
+
+
+class UserIdManager(Manager):
+    def get_item(self, id, user, **kwargs):
+        return super().get(id=int(id),user=user, **kwargs)
 
 def all_models(app=None):
     if app:

+ 61 - 0
src/djangotools/common/module_loader.py

@@ -0,0 +1,61 @@
+from pathlib import Path
+
+from django.core.exceptions import ImproperlyConfigured
+
+
+def get_module(data):
+    if isinstance(data, str):
+        data = __import__(data, fromlist=['object'])
+    children = []
+    path = Path(data.__file__)
+    module_name = data.__name__
+    if path.name == "__init__.py":
+        path = path.parent
+        for f in path.iterdir():
+            if f.is_file() and f.name.lower().endswith(".py") and f.name != "__init__.py":
+                children.append(f"{module_name}.{f.name[:-3]}")
+            elif f.is_dir() and f.name  != "__pycache__":
+                if (f / "__init__.py").is_file():
+                    children.append(f"{module_name}.{f.name}")
+    return data, children
+
+
+def iter_module_content(roots, max_depth=None, element_type=None, classe=None, filter_attr=None, only_module=False):
+    if isinstance(roots, str):
+        roots = [roots]
+    if classe is not None:
+        element_type = type
+
+    done = set()
+    stack = [(x, 0) for x in roots]
+    while stack:
+        module, depth = stack.pop(0)
+        if max_depth is not None and depth > max_depth: continue
+
+        current, children = get_module(module)
+        if current in done: continue
+
+        if only_module:
+            yield current
+        elif filter_attr is None:
+            for attr in dir(current):
+                val = getattr(current, attr)
+                try:
+                    if ((element_type is None or isinstance(val, element_type)) and
+                            (classe is None or issubclass(val, classe))):
+                        yield attr, val
+                except ImproperlyConfigured:
+                    pass
+        else:
+            for attr in dir(current):
+                val = getattr(current, attr)
+                if filter_attr(current, attr, val):
+                    yield attr, val
+
+
+
+        stack.extend([ (x, depth+1) for x in children])
+        done.add(current)
+
+def iter_module(roots, max_depth=None):
+    return iter_module_content(roots=roots, max_depth=max_depth, only_module=True)

+ 4 - 2
src/djangotools/common/path.py

@@ -1,6 +1,8 @@
 from pathlib import Path
 
-from djangotools.model import tools
+from djangotools.common import models
+
+
 
 
 class UrlPath:
@@ -105,7 +107,7 @@ class DeferredModelPath(UrlPath):
 
 
     def resolve(self):
-        model = tools.get_model(self.model)
+        model = models.get_model(self.model)
         root = model._path_.resolve() if model._path_ else PointPath()
 
         attribute = AttributePath(model, model._key_)

+ 14 - 0
src/djangotools/config/__init__.py

@@ -1,6 +1,18 @@
 from djangotools.config.base import ConfigLoader
 import os
 
+"""
+# app setting file
+
+with NoConfigLoader():
+    # si les settings appellent déja ConfigLoader
+    from project.settings  import *
+[...]
+CONFIG = load_settings(globals())
+[...]
+
+"""
+from djangotools.config.base import NoConfigLoader
 settings = None
 app_dir = None
 
@@ -24,8 +36,10 @@ def load_app(settings_module, app_dir=None, populate=True):
     if loaded: return
     loaded = True
 
+
     if app_dir:
         os.environ["APP_DIR"] = str(app_dir)
+    print("Print loading app_dir ", os.environ.get("APP_DIR"))
 
     if not "DJANGO_SETTINGS_MODULE" in os.environ:
         os.environ.setdefault('DJANGO_SETTINGS_MODULE', settings_module)

+ 9 - 7
src/djangotools/config/base.py

@@ -26,14 +26,7 @@ class DeferredSetting:
     def __call__(self, settings):
         return self.function(settings)
 
-"""
-# app setting file
 
-app_dir = AppDir(globals(), ALLOW_AUTO_LOGIN=False, )
-
-
-
-"""
 
 disable_config_loader = False
 
@@ -78,6 +71,15 @@ class ConfigLoader:
         ("TEMPLATES_DIR", DeferredSetting(lambda x: [get_option(x, "BASE_DIR") / "frontend" / "build"])),
         ("ALLOWED_HOSTS", ["*"]),
         ("ROOT_URLCONF", "djangotools.urls"),
+        ("LOGIN_TEMPLATE", "login.html"),
+        ("ROOT_TEMPLATE", "index.html"),
+        ("LOGIN_CONTEXT", "login"),
+        ("LOGIN_URL", "login"),
+        ("LOGOUT_URL", "disconnect"),
+        ("AUTH_URL", "auth"),
+        ("COMMANDS_MODULES", []),
+        ("CONTEXT_MODULES", []),
+        ("VIEWS_MODULES", ["djangotools.views"]),
         ("CORS_ORIGIN_ALLOW_ALL", True),
         ("DEFAULT_AUTO_FIELD", 'django.db.models.BigAutoField'),
         ("AUTO_LOGIN", DeferredSetting(lambda x: os.environ["USER"])),

+ 33 - 0
src/djangotools/migrations/0001_initial.py

@@ -0,0 +1,33 @@
+# Generated by Django 4.2.4 on 2023-09-21 09:26
+
+from django.conf import settings
+from django.db import migrations, models
+import django.db.models.deletion
+import djangotools.models.serializedmodel
+import djangotools.views.auto_path
+
+
+class Migration(migrations.Migration):
+
+    initial = True
+
+    dependencies = [
+        migrations.swappable_dependency(settings.AUTH_USER_MODEL),
+    ]
+
+    operations = [
+        migrations.CreateModel(
+            name='Preference',
+            fields=[
+                ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+                ('profile', models.TextField(default='default')),
+                ('key', models.TextField()),
+                ('value', models.TextField(blank=True, null=True)),
+                ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
+            ],
+            options={
+                'unique_together': {('user', 'profile', 'key')},
+            },
+            bases=(models.Model, djangotools.models.serializedmodel.SerializableModel, djangotools.views.auto_path.AutoPath),
+        ),
+    ]

+ 0 - 0
src/djangotools/model/__init__.py → src/djangotools/migrations/__init__.py


+ 7 - 0
src/djangotools/models/__init__.py

@@ -0,0 +1,7 @@
+
+from djangotools.common.models import UserIdManager, Manager
+from djangotools.models.preference import Preference, UserPreferenceManager
+
+from djangotools.models.serializedmodel import Serializer, DateSerializer, ManyToOneSerializer,\
+    BypassProperty, FunctionSerilizer, PropertySerializer, compare_dict, compare_list_dict,\
+    SerializeField,  serialize, SerializableModel

+ 82 - 0
src/djangotools/models/preference.py

@@ -0,0 +1,82 @@
+import json
+
+from django.contrib.auth.models import User
+from django.db import models
+from djangotools.common.path import DeferredModelPath
+from djangotools.models.serializedmodel import SerializableModel
+from djangotools.models import UserIdManager
+
+from djangotools.views.auto_path import AutoPath
+
+
+class UserPreferenceManager:
+
+    def __init__(self, user, profile="default"):
+        self.user=user
+        self.profile=profile
+
+    def update(self, data):
+        for k, v in data.items():
+            self.set(k, v)
+
+    def set(self, key, value):
+        pref = self._get(key)
+        if pref:
+            pref.set(value)
+            return pref
+        return Preference.objects.create(
+                user=self.user,
+                profile=self.profile,
+                key=key,
+                value = json.dumps(value)
+            )
+
+    def get(self, key, default=None):
+        pref = self._get(key)
+        if pref is not None:
+            return pref.get()
+        return self.set(key, default)
+
+
+    def _get(self, key):
+        try:
+            return Preference.objects.get(user=self.user, profile=self.profile, key=key)
+        except Preference.DoesNotExist:
+            return None
+
+
+    @property
+    def dict(self):
+        if not self.user.is_authenticated:
+            return {}
+        return {
+            x.key : x.get() for x in Preference.objects.filter(user=self.user, profile=self.profile)
+        }
+
+class Preference(models.Model, SerializableModel, AutoPath):
+    _path_ = None
+
+
+    objects = UserIdManager()
+    id : int
+    user : User = models.ForeignKey(User, on_delete=models.CASCADE)
+    profile : str = models.TextField(default="default")
+    key : str = models.TextField()
+    value : str = models.TextField(null=True, blank=True)
+
+    class Meta:
+        unique_together = ['user', 'profile', "key"]
+
+    def set(self, value):
+        self.value=json.dumps(value)
+        self.save()
+
+    def __str__(self):
+        return f"{self.key} : {json.dumps(self.value)}  "
+
+    def __repr__(self): return self.__str__()
+
+    def get(self):
+        return json.loads(self.value) if self.value is not None else None
+
+Preference.register()

+ 2 - 10
src/djangotools/model/serializedmodel.py → src/djangotools/models/serializedmodel.py

@@ -3,15 +3,7 @@ from collections.abc import Iterable
 from typing import Callable
 
 from django.db import models
-
-
-
-class Manager(models.Manager):
-    FIELD_CREATE_ITEM=()
-    def create_from_json(self, **kwargs):
-        return self.create(**kwargs)
-
-
+from djangotools.common import BaseSerializationModel
 
 
 class Serializer:
@@ -124,7 +116,7 @@ class serialize:
     def __call__(self, fct):
         return SerializeField(fct, *self.args, **self.kwargs)
 
-class SerializableModel:
+class SerializableModel(BaseSerializationModel):
     class Empty:
         pass
 

+ 2 - 3
src/djangotools/urls.py

@@ -1,7 +1,7 @@
 from django.urls import path
 
-from djangotools.view.auto_path import AutoPathManager
-from djangotools.view.router import Router
+from djangotools.views.auto_path import AutoPathManager
+from djangotools.views.router import Router
 from django.contrib import admin
 
 class RouterIterator:
@@ -11,7 +11,6 @@ class RouterIterator:
 
     def __iter__(self):
         if not self._cache:
-            print("loaded")
             self._cache= [path('admin/', admin.site.urls)] + AutoPathManager.get_instance().get_pathes() + Router.get_pathes()
         return iter(self._cache)
 

+ 9 - 0
src/djangotools/views/__init__.py

@@ -0,0 +1,9 @@
+from djangotools.common.path import DeferredModelPath
+
+from djangotools.views.misc import render_page
+from djangotools.views.route import request_wrapper, Route, RegisteredRoute
+from djangotools.views.router import Router
+from djangotools.common import response
+from djangotools.common import errors
+from djangotools.views.auto_path import AutoPath, PointPath, ModelPath,  Get, Put, ConstPath, AttributePath
+from djangotools.views.context.base import ContextData, ActionParams

+ 3 - 3
src/djangotools/view/auto_path.py → src/djangotools/views/auto_path.py

@@ -6,8 +6,8 @@ from django.urls import path
 
 from djangotools.common import response
 from djangotools.common.path import PointPath, ConstPath, AttributePath, UrlPath, ModelPath
-from djangotools.common.route import RegisteredRoute, Post, Delete, Route, Get, Put
-from djangotools.model import tools
+from djangotools.views.route import RegisteredRoute, Post, Delete, Route, Get, Put
+from djangotools.common import models
 
 
 class AutoPath:
@@ -84,7 +84,7 @@ class AutoPathManager:
 
     def __init__(self, app=None):
         self._models = {}
-        for name, model in tools.iter_models_items():
+        for name, model in models.iter_models_items():
             if isinstance(model, type) and issubclass(model, AutoPath):
                 self._models[model.__name__] = model
         self.routes = defaultdict(dict)

+ 0 - 0
src/djangotools/view/__init__.py → src/djangotools/views/context/__init__.py


+ 160 - 0
src/djangotools/views/context/base.py

@@ -0,0 +1,160 @@
+from django.conf import settings
+
+from djangotools.common.errors import UnauthorizedException
+import datetime
+
+
+import sys
+from collections import defaultdict
+from pathlib import Path
+
+
+import json
+
+from djangotools.common.errors import UnauthorizedException
+
+from django.conf import settings
+from djangotools.common.module_loader import iter_module_content
+
+allow_all=False
+
+
+def get_settings(request):
+    if settings.PRODUCTION:
+        return {
+            "url": ""
+        }
+    else:
+        return {
+                "url" : f"http://localhost:{request.META['SERVER_PORT']}",
+            }
+
+class ContextData:
+    page = None
+    need_auth = True
+    redirect = True
+    action_params = True
+
+    default_action = None
+
+    def is_mobile(self, request):
+        ua = request.headers.get("user-agent", "").lower()
+        return any(x in ua for x in [
+            "mobile", "android", "iphone"
+        ])
+
+    def get_context(self, request,  **kwargs):
+
+        from djangotools.models.preference import UserPreferenceManager
+        if ( not allow_all and self.need_auth) and not request.user.is_authenticated:
+            raise UnauthorizedException("Cette ressource nécessite une authentification")
+
+        body = request.POST or (json.loads(request.body) if  request.body else None)
+        if "post_action" in kwargs:
+            body = kwargs["post_action"]
+
+        post_data = body or self.default_action
+        if post_data and "action" in post_data:
+            action_params = PostParameters(request, post_data, **kwargs) if (body or self.default_action) and self.action_params else None
+        else:
+            action_params = None
+        include_data = kwargs.get("include_data", True)
+
+        if include_data:
+            data = self.get_context_data(request, action_params, **kwargs)
+        else:
+            data = {}
+        if "data" in kwargs: data.update(kwargs.pop("data"))
+        pref = UserPreferenceManager(request.user)
+        return {
+            "context" : {
+                "page" : self.page,
+                "data" : data,
+                "user" : {
+                    "name" : request.user.username,
+                    "id" : request.user.id,
+                    "preferences" : pref.dict,
+                    "profile" : pref.profile,
+                    "device": {
+                        "mobile" : self.is_mobile(request)
+                    }
+                },
+                "server" : get_settings(request)
+,                **kwargs
+            }
+        }
+
+    def get_context_data(self, request, action_params,  **kwargs):
+        return {}
+
+
+
+CONTEXT="context"
+ACTION_PARAMS="action"
+def load_module(module):
+
+    # module_path = "mypackage.%s" % module
+    module_path = module
+
+    if module_path in sys.modules:
+        return sys.modules[module_path]
+
+    return __import__(module_path, fromlist=[module])
+
+
+
+def load_modules():
+    global modules
+    for name, curr in iter_module_content(settings.CONTEXT_MODULES, classe=(ContextData, ActionParams)):
+        if issubclass(curr, ContextData):
+            modules[CONTEXT][curr.page] = curr
+        else:
+            if isinstance(curr.action, str):
+                modules[ACTION_PARAMS][curr.action] = curr
+            elif isinstance(curr.action, (list, tuple)):
+                for act in curr.action:
+                    modules[ACTION_PARAMS][act] = curr
+
+def get_context_class(name):
+    if name in modules[CONTEXT]:
+        return modules[CONTEXT][name]
+
+def get_action_params_class(name):
+    if name in modules[ACTION_PARAMS]:
+        return modules[ACTION_PARAMS][name]
+
+
+class ActionParams:
+
+    action = None
+
+    def __init__(self, request, action, data, **kwargs):
+        self._action = action
+        self._data = data
+        self._request=request
+
+    @property
+    def user(self):
+        return self._request.user
+
+
+    def get(self, name, default=None):
+        return self._data.get(name, default)
+
+
+class PostParameters:
+
+    def __init__(self, request, content_data, **kwargs):
+        self._data = content_data
+        self.action = content_data["action"]
+        self.request = request
+        action_class = get_action_params_class(self.action)
+        if not action_class:
+            raise ValueError(f"Action '{self.action}' inconnue....")
+        self.params = action_class(request, self.action, content_data["params"], **kwargs)
+        self.options = content_data.get("options", {})
+
+
+
+modules = defaultdict(dict)
+

+ 19 - 0
src/djangotools/views/context/view.py

@@ -0,0 +1,19 @@
+def _render(request, name, *args, **kwargs):
+    context_class = get_context_class(name)
+    if context_class is None:
+        raise Exception()
+
+    context = context_class()
+    try:
+        data = context.get_context(request, *args, **kwargs, include_data=False)
+        data["context"] = json.dumps(data["context"])
+        print("context", context.need_auth, data)
+    except UnauthorizedException as err:
+        return _render(request, "login", data={"error" : str(err)})
+
+    data.update({"debug": False})
+    ret = render(request, "index.html", data)
+    return ret
+
+def render_page(name):
+    return lambda request, *args, name=name, **kwargs: _render(request, name, *args, **kwargs)

+ 96 - 0
src/djangotools/views/misc.py

@@ -0,0 +1,96 @@
+import json
+import traceback
+
+from django.conf import settings
+from django.contrib.auth import login, authenticate, logout
+from django.contrib.auth.models import User
+from django.http import HttpResponseRedirect
+from django.shortcuts import render
+
+from djangotools.common import response
+from djangotools.common.errors import CustomException, UnauthorizedException
+from djangotools.views.context.base import get_context_class
+from djangotools.views.router import Router
+from djangotools.views.utils import debug_login
+
+
+def context(page, data, **kwargs):
+    return {
+        "context": json.dumps({
+            "page" : page,
+            "data" : data,
+            **kwargs
+        })
+    }
+
+
+
+@Router.post(settings.CONTEXT_URL)
+def raw_context(request, name):
+    debug_login(request)
+    context_class = get_context_class(name)
+
+    data = json.loads(request.body) or {}
+    url = data.get("url_data", {})
+    post = data.get("post", {})
+    if context_class is None:
+        raise ValueError(f"Impossible de trouver le contexte : {name}")
+    context = context_class()
+
+    try:
+        data = context.get_context(request, **url, post_action=post)
+    except UnauthorizedException as err:
+        return response.serv_json_not_logged()
+
+
+    return response.serv_json_ok(data)
+
+
+def _render(request, name, page=settings.ROOT_TEMPLATE, *args, **kwargs):
+    context_class = get_context_class(name)
+
+    if context_class:
+        context = context_class()
+        try:
+            data = context.get_context(request, *args, **kwargs, include_data=False)
+            data["context"] = json.dumps(data["context"])
+            print("context", context.need_auth, data)
+        except UnauthorizedException as err:
+            return _render(request, settings.LOGIN_CONTEXT, settings.LOGIN_TEMPLATE, data={"error" : str(err)})
+    else:
+        data = {}
+    data.update({"debug": False})
+    ret = render(request, page, data)
+    return ret
+
+def render_page(name, page=settings.ROOT_TEMPLATE):
+    return lambda request, *args, name=name, page=page, **kwargs: _render(request, name, page=page, *args, **kwargs)
+
+@Router.get(settings.LOGOUT_URL, need_auth=True)
+def disconnect(request, user):
+    redirect = request.GET.get("redirect", settings.LOGIN_URL, need_auth=False)
+    logout(request)
+    return HttpResponseRedirect(redirect)
+
+
+Router.get(settings.LOGIN_URL)(render_page(settings.LOGIN_CONTEXT, settings.LOGIN_TEMPLATE))
+
+
+@Router.get(settings.AUTH_URL)
+def auth(request):
+    params = request.POST
+    redirect = request.GET.get("redirect", "/")
+    user = params.get("login", None)
+    password = params.get("password", None)
+    if request.user and request.user.is_authenticated:
+        return HttpResponseRedirect(redirect)
+
+    if user is not None and password is not None:
+
+        user = authenticate(request, username=user, password=password)
+        if user is not None:
+            login(request, user)
+            return HttpResponseRedirect(redirect)
+    users = [k.username for k in User.objects.all()]
+    return _render(request, settings.LOGIN_CONTEXT, settings.LOGIN_TEMPLATE, data={"error" : f"Mauvais login ou mot de passe"})
+

+ 50 - 10
src/djangotools/common/route.py → src/djangotools/views/route.py

@@ -1,11 +1,14 @@
+import traceback
+
 from django.contrib.auth.models import User
 from django.db.models import Manager, Model, QuerySet
 from django.db.models.base import ModelBase
 
 from djangotools.common import response
-from djangotools.common.path import PointPath, ModelPath, AttributePath
-from djangotools.model.serializedmodel import SerializableModel
-from djangotools.view.utils import debug_login
+from djangotools.common.errors import CustomException
+from djangotools.common.path import PointPath, ModelPath, AttributePath, ConstPath
+from djangotools.models.serializedmodel import SerializableModel
+from djangotools.views.utils import debug_login
 
 
 class Route:
@@ -14,9 +17,12 @@ class Route:
     DELETE = "DELETE"
     PUT = "PUT"
     method  = {GET, POST, DELETE, PUT}
-    def __init__(self, route=None,  method=None):
+    def __init__(self, route=None,  method=None, **options):
+        self.options = options
         self.method = method if method is not None else self.method
         if isinstance(method, str): self.method = {method}
+        if isinstance(route, str):
+            route = ConstPath(route)
         self.path = route or PointPath()
 
     def __call__(self, *args, **kwargs):
@@ -30,21 +36,46 @@ class Route:
         raise Exception()
 
 
+def request_wrapper(fct):
+    def wrapper(request, *args, **kwargs):
+        try:
+            return fct(request, *args, **kwargs)
+        except CustomException as err:
+            return err.get_error()
+        except Exception as err:
+            traceback.print_exc()
+            return response.serv_json(500, 500, err.__class__.__name__, str(err))
+
+    return  wrapper
 
 class RegisteredRoute:
+
+
     _default_user_id_ = 1
-    def __init__(self, path : Route, callback, is_manager=False, is_method=True, **options):
+    def __init__(self, path : Route, callback, is_manager=False, is_method=True):
+        """
+
+        :param path:
+        :param callback:
+        :param is_manager:
+        :param is_method:
+        :param options:
+            need_auth: active l'authentitficatyion
+            no_wrap: ne wrappe pas les callbacks (pour les captures d'exception)
+        """
         self.route = path if path is not None else Route()
-        self.callback = callback
+
         self.is_manager = is_manager
         self.is_method = is_method
-        self.options = options
+        self.options = self.route.options
+        if not "no_wrap" in self.options: callback = request_wrapper(callback)
+        self.callback = callback
 
-    def __call__(self, req, *args, **kwargs):
+    def _call(self, req, *args, **kwargs):
         obj = None
         debug_login(req, self._default_user_id_)
         user = req.user
-        if "need_auth" and  not user.is_authenticated:
+        if self.options.get("need_auth", False) and  not user.is_authenticated:
             return response.serv_json_not_logged()
 
 
@@ -78,7 +109,10 @@ class RegisteredRoute:
         if self.is_method:
             ret = self.callback(obj, req, *args, **kwargs)
         else:
-            ret = self.callback(req, obj, *args, **kwargs)
+            if obj is None:
+                ret = self.callback(req, *args, **kwargs)
+            else:
+                ret = self.callback(req, obj, *args, **kwargs)
 
         if isinstance(ret, QuerySet):
             return [x.serialize() for x in ret]
@@ -89,6 +123,12 @@ class RegisteredRoute:
         return ret
 
 
+    def __call__(self, req, *args, **kwargs):
+        if "no_wrap" in self.options:
+            return self._call(req, *args, **kwargs)
+        return request_wrapper(self._call)(req, *args, **kwargs)
+
+
 
     def copy(self):
         return RegisteredRoute(self.route, self.callback, is_manager=self.is_manager,

+ 8 - 10
src/djangotools/view/router.py → src/djangotools/views/router.py

@@ -3,27 +3,26 @@ from functools import partial
 
 from django.db.models.base import ModelBase
 from django.http import HttpRequest, HttpResponse
-from django.urls import path, URLPattern
+from django.urls import URLPattern
 
-from djangotools.common import response
-from djangotools.common.route import RegisteredRoute, Route
+from djangotools.common import response, BaseSerializationModel
+from djangotools.views.route import RegisteredRoute, Route
 from djangotools import  DjangoTools
-from djangotools.model.serializedmodel import SerializableModel
-from djangotools.model.tools import iter_models
-
+from djangotools.common.models import iter_models
 from django.urls import path
+
 class _Router:
 
     class _Route(Route):
-        def __call__(self, fct, **options):
-            rr =RegisteredRoute(self, fct, is_method=False, **options)
+        def __call__(self, fct):
+            rr =RegisteredRoute(self, fct, is_method=False)
             Router.add(rr)
             return rr
 
     def __init__(self):
         self._models = {}
         for  model in iter_models(DjangoTools.app):
-            if isinstance(model, type) and issubclass(model, SerializableModel):
+            if isinstance(model, type) and issubclass(model, BaseSerializationModel):
                 self._models[model.__name__] = model
         self.routes = defaultdict(dict)
         self._raw_pathes = []
@@ -50,7 +49,6 @@ class _Router:
         return self
 
     def add(self, registerd_route, override=True):
-        print("add", registerd_route)
         if isinstance(registerd_route, RegisteredRoute):
             for method in registerd_route.route.method:
                 current_path = registerd_route.route.path.get_path()

+ 1 - 1
src/djangotools/view/utils.py → src/djangotools/views/utils.py

@@ -1,7 +1,7 @@
 from django.contrib.auth import login
 from django.contrib.auth.models import User
 
-from djangotools.config import settings
+from django.conf import settings
 
 def debug_login(request, fallback_id=None):
     if settings.ALLOW_AUTO_LOGIN and not request.user.is_authenticated and settings.AUTO_LOGIN: