Browse Source

Premier commit

fanch 1 year ago
parent
commit
ae22c8cb38

+ 1 - 1
setup.py

@@ -9,7 +9,7 @@ install_requires = [
 ]
 ]
 
 
 tests_require = [
 tests_require = [
-    "fab",
+    "mkfab",
     "pytest"
     "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("-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("-b", "--bind",  help="Adresse d'écoute du serveur exemple localhost:8080"),
         Argument("-C", "--no-cron", action="store_true", help="Ne pas lancer le cron"),
         Argument("-C", "--no-cron", action="store_true", help="Ne pas lancer le cron"),
+
     ]
     ]
 
 
     def start_cron(self, hostname):
     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):
     def __init__(self, commands_dirs):
         super().__init__()
         super().__init__()
+        if isinstance(commands_dirs, str): commands_dirs = [commands_dirs]
         self.commands_dirs = commands_dirs
         self.commands_dirs = commands_dirs
         parser = self.add_subparsers(parser_class=argparse.ArgumentParser)
         parser = self.add_subparsers(parser_class=argparse.ArgumentParser)
         self.add_argument("-a", "--app-dir",  help="Dossier de données du serveur")
         self.add_argument("-a", "--app-dir",  help="Dossier de données du serveur")
         self.add_argument("-s", "--settings",  help="Module de settings à utiliser")
         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 = {}
             kwargs = {}
             if cmd.HELP: kwargs["help"] = cmd.HELP
             if cmd.HELP: kwargs["help"] = cmd.HELP
             if cmd.ALIASES: kwargs["aliases"] = cmd.ALIASES
             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 django.core.exceptions import ImproperlyConfigured
 
 
+from djangotools.common.module_loader import iter_module_content
+
 
 
 class CommandData:
 class CommandData:
 
 
@@ -50,44 +52,13 @@ class Command:
         thread.start()
         thread.start()
         return thread
         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
     @staticmethod
     def load(commands_dirs=None):
     def load(commands_dirs=None):
-        if isinstance(commands_dirs, str): commands_dirs = [commands_dirs]
-        from djangotools.cmdline import commands
         ret = {}
         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())
         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.apps import apps
 from django.db.models.base import ModelBase, Model
 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):
 def all_models(app=None):
     if app:
     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 pathlib import Path
 
 
-from djangotools.model import tools
+from djangotools.common import models
+
+
 
 
 
 
 class UrlPath:
 class UrlPath:
@@ -105,7 +107,7 @@ class DeferredModelPath(UrlPath):
 
 
 
 
     def resolve(self):
     def resolve(self):
-        model = tools.get_model(self.model)
+        model = models.get_model(self.model)
         root = model._path_.resolve() if model._path_ else PointPath()
         root = model._path_.resolve() if model._path_ else PointPath()
 
 
         attribute = AttributePath(model, model._key_)
         attribute = AttributePath(model, model._key_)

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

@@ -1,6 +1,18 @@
 from djangotools.config.base import ConfigLoader
 from djangotools.config.base import ConfigLoader
 import os
 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
 settings = None
 app_dir = None
 app_dir = None
 
 
@@ -24,8 +36,10 @@ def load_app(settings_module, app_dir=None, populate=True):
     if loaded: return
     if loaded: return
     loaded = True
     loaded = True
 
 
+
     if app_dir:
     if app_dir:
         os.environ["APP_DIR"] = str(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:
     if not "DJANGO_SETTINGS_MODULE" in os.environ:
         os.environ.setdefault('DJANGO_SETTINGS_MODULE', settings_module)
         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):
     def __call__(self, settings):
         return self.function(settings)
         return self.function(settings)
 
 
-"""
-# app setting file
 
 
-app_dir = AppDir(globals(), ALLOW_AUTO_LOGIN=False, )
-
-
-
-"""
 
 
 disable_config_loader = False
 disable_config_loader = False
 
 
@@ -78,6 +71,15 @@ class ConfigLoader:
         ("TEMPLATES_DIR", DeferredSetting(lambda x: [get_option(x, "BASE_DIR") / "frontend" / "build"])),
         ("TEMPLATES_DIR", DeferredSetting(lambda x: [get_option(x, "BASE_DIR") / "frontend" / "build"])),
         ("ALLOWED_HOSTS", ["*"]),
         ("ALLOWED_HOSTS", ["*"]),
         ("ROOT_URLCONF", "djangotools.urls"),
         ("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),
         ("CORS_ORIGIN_ALLOW_ALL", True),
         ("DEFAULT_AUTO_FIELD", 'django.db.models.BigAutoField'),
         ("DEFAULT_AUTO_FIELD", 'django.db.models.BigAutoField'),
         ("AUTO_LOGIN", DeferredSetting(lambda x: os.environ["USER"])),
         ("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 typing import Callable
 
 
 from django.db import models
 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:
 class Serializer:
@@ -124,7 +116,7 @@ class serialize:
     def __call__(self, fct):
     def __call__(self, fct):
         return SerializeField(fct, *self.args, **self.kwargs)
         return SerializeField(fct, *self.args, **self.kwargs)
 
 
-class SerializableModel:
+class SerializableModel(BaseSerializationModel):
     class Empty:
     class Empty:
         pass
         pass
 
 

+ 2 - 3
src/djangotools/urls.py

@@ -1,7 +1,7 @@
 from django.urls import path
 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
 from django.contrib import admin
 
 
 class RouterIterator:
 class RouterIterator:
@@ -11,7 +11,6 @@ class RouterIterator:
 
 
     def __iter__(self):
     def __iter__(self):
         if not self._cache:
         if not self._cache:
-            print("loaded")
             self._cache= [path('admin/', admin.site.urls)] + AutoPathManager.get_instance().get_pathes() + Router.get_pathes()
             self._cache= [path('admin/', admin.site.urls)] + AutoPathManager.get_instance().get_pathes() + Router.get_pathes()
         return iter(self._cache)
         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 import response
 from djangotools.common.path import PointPath, ConstPath, AttributePath, UrlPath, ModelPath
 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:
 class AutoPath:
@@ -84,7 +84,7 @@ class AutoPathManager:
 
 
     def __init__(self, app=None):
     def __init__(self, app=None):
         self._models = {}
         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):
             if isinstance(model, type) and issubclass(model, AutoPath):
                 self._models[model.__name__] = model
                 self._models[model.__name__] = model
         self.routes = defaultdict(dict)
         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.contrib.auth.models import User
 from django.db.models import Manager, Model, QuerySet
 from django.db.models import Manager, Model, QuerySet
 from django.db.models.base import ModelBase
 from django.db.models.base import ModelBase
 
 
 from djangotools.common import response
 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:
 class Route:
@@ -14,9 +17,12 @@ class Route:
     DELETE = "DELETE"
     DELETE = "DELETE"
     PUT = "PUT"
     PUT = "PUT"
     method  = {GET, POST, DELETE, 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
         self.method = method if method is not None else self.method
         if isinstance(method, str): self.method = {method}
         if isinstance(method, str): self.method = {method}
+        if isinstance(route, str):
+            route = ConstPath(route)
         self.path = route or PointPath()
         self.path = route or PointPath()
 
 
     def __call__(self, *args, **kwargs):
     def __call__(self, *args, **kwargs):
@@ -30,21 +36,46 @@ class Route:
         raise Exception()
         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:
 class RegisteredRoute:
+
+
     _default_user_id_ = 1
     _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.route = path if path is not None else Route()
-        self.callback = callback
+
         self.is_manager = is_manager
         self.is_manager = is_manager
         self.is_method = is_method
         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
         obj = None
         debug_login(req, self._default_user_id_)
         debug_login(req, self._default_user_id_)
         user = req.user
         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()
             return response.serv_json_not_logged()
 
 
 
 
@@ -78,7 +109,10 @@ class RegisteredRoute:
         if self.is_method:
         if self.is_method:
             ret = self.callback(obj, req, *args, **kwargs)
             ret = self.callback(obj, req, *args, **kwargs)
         else:
         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):
         if isinstance(ret, QuerySet):
             return [x.serialize() for x in ret]
             return [x.serialize() for x in ret]
@@ -89,6 +123,12 @@ class RegisteredRoute:
         return ret
         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):
     def copy(self):
         return RegisteredRoute(self.route, self.callback, is_manager=self.is_manager,
         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.db.models.base import ModelBase
 from django.http import HttpRequest, HttpResponse
 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 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
 from django.urls import path
+
 class _Router:
 class _Router:
 
 
     class _Route(Route):
     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)
             Router.add(rr)
             return rr
             return rr
 
 
     def __init__(self):
     def __init__(self):
         self._models = {}
         self._models = {}
         for  model in iter_models(DjangoTools.app):
         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._models[model.__name__] = model
         self.routes = defaultdict(dict)
         self.routes = defaultdict(dict)
         self._raw_pathes = []
         self._raw_pathes = []
@@ -50,7 +49,6 @@ class _Router:
         return self
         return self
 
 
     def add(self, registerd_route, override=True):
     def add(self, registerd_route, override=True):
-        print("add", registerd_route)
         if isinstance(registerd_route, RegisteredRoute):
         if isinstance(registerd_route, RegisteredRoute):
             for method in registerd_route.route.method:
             for method in registerd_route.route.method:
                 current_path = registerd_route.route.path.get_path()
                 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 import login
 from django.contrib.auth.models import User
 from django.contrib.auth.models import User
 
 
-from djangotools.config import settings
+from django.conf import settings
 
 
 def debug_login(request, fallback_id=None):
 def debug_login(request, fallback_id=None):
     if settings.ALLOW_AUTO_LOGIN and not request.user.is_authenticated and settings.AUTO_LOGIN:
     if settings.ALLOW_AUTO_LOGIN and not request.user.is_authenticated and settings.AUTO_LOGIN: