Commit 1a6b1577 authored by Thomas Mignot's avatar Thomas Mignot

first commit

parent 0d06c43c
# pyfront
Meteorish django responsive frontend
\ No newline at end of file
Meteorish django responsive frontend
you first need to install:
- django 2.1
- django_channels
- channels_redis
- redis-server (up and running)
then juste run ./manage.py runserver from the root directory of this project
for now, it's juste possible ton insert from the commad line in the ./manage.py shell
just the time to add some button ;)
#!/usr/bin/env python
import os
import sys
if __name__ == '__main__':
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'project.settings')
try:
from django.core.management import execute_from_command_line
except ImportError as exc:
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?"
) from exc
execute_from_command_line(sys.argv)
import jsonpickle
import uuid
class Component():
def __init__(self,
tag='div',
content=None,
attr=None,
events=None,
parent='body',
_id=uuid.uuid1().hex):
self._id = _id
self.parent = parent
self.tag = 'HTML' if parent is None else tag
self.attr = {} if attr is None else attr
self.events = {} if events is None else events
self.content = [] if content is None else content
# handle text node as content
if isinstance(content, list):
for c in self.content:
c.parent = self._id
elif isinstance(content, str) and tag is not 'text':
self.content = [Text(content)]
def addchild(self, component):
component.parent = self._id
self.content.append(component)
def addchildren(self, components):
for component in components:
self.addchild(component)
def addevents(self, events):
self.events.update(events)
def to_json(self):
return jsonpickle.encode(self)
def to_obj(self):
return {
'_id': self._id,
'tag': self.tag,
'content': [
c.to_obj()
for c in self.content
] if self.tag != 'text' else self.content,
'parent': self.parent,
'events': self.events,
'attr': self.attr,
'subscriptions': getattr(self, 'subscriptions', [])
}
class Div(Component):
def __init__(self, content=[], attr={}, events={},
parent='body', _id=uuid.uuid1().hex):
super().__init__('div', content, attr, events, parent, _id)
class Ul(Component):
def __init__(self, content=[], attr={}, events={},
parent='body', _id=uuid.uuid1().hex):
super().__init__('ul', content, attr, events, parent, _id)
class Ol(Component):
def __init__(self, content=[], attr={}, events={},
parent='body', _id=uuid.uuid1().hex):
super().__init__('ol', content, attr, events, parent, _id)
class Li(Component):
def __init__(self, content=[], attr={}, events={},
parent='body', _id=uuid.uuid1().hex):
super().__init__('li', content, attr, events, parent, _id)
class Span(Component):
def __init__(self, content=[], attr={}, events={},
parent='body', _id=uuid.uuid1().hex):
super().__init__('span', content, attr, events, parent, _id)
class Text(Component):
def __init__(self, content=[],
parent='body', _id=uuid.uuid1().hex):
super().__init__('text', content, parent=parent, _id=_id)
from channels.routing import ProtocolTypeRouter, URLRouter
from channels.auth import AuthMiddlewareStack
import todos.routing
application = ProtocolTypeRouter({
'websocket': AuthMiddlewareStack(
URLRouter(
todos.routing.websocket_urlpatterns
)
),
})
"""
Django settings for project project.
Generated by 'django-admin startproject' using Django 2.1.7.
For more information on this file, see
https://docs.djangoproject.com/en/2.1/topics/settings/
For the full list of settings and their values, see
https://docs.djangoproject.com/en/2.1/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/2.1/howto/deployment/checklist/
# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = 'ti$t!2!q!c1b*^!zsldiys#&y-_gu1^mtpy$juoumc4*wk^o+_'
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True
ALLOWED_HOSTS = []
# Application definition
INSTALLED_APPS = [
'channels',
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'todos',
]
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 = 'project.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',
],
},
},
]
ASGI_APPLICATION = 'project.routing.application'
CHANNEL_LAYERS = {
"default": {
"BACKEND": "channels_redis.core.RedisChannelLayer",
"CONFIG": {
"hosts": [("localhost", 6379)],
},
},
}
# Database
# https://docs.djangoproject.com/en/2.1/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/2.1/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/2.1/topics/i18n/
LANGUAGE_CODE = 'en-us'
TIME_ZONE = 'UTC'
USE_I18N = True
USE_L10N = True
USE_TZ = True
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/2.1/howto/static-files/
STATIC_URL = '/static/'
"""project URL Configuration
The `urlpatterns` list routes URLs to views. For more information please see:
https://docs.djangoproject.com/en/2.1/topics/http/urls/
Examples:
Function views
1. Add an import: from my_app import views
2. Add a URL to urlpatterns: path('', views.home, name='home')
Class-based views
1. Add an import: from other_app.views import Home
2. Add a URL to urlpatterns: path('', Home.as_view(), name='home')
Including another URLconf
1. Import the include() function: from django.urls import include, path
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
"""
from django.conf.urls import include, url
urlpatterns = [
url('^', include('todos.urls')),
]
from django.apps import AppConfig
class TodosConfig(AppConfig):
name = 'todos'
from project.component import Div, Ul, Li, Span
from todos.models import Tasks
class Task(Li):
def __init__(self, task_object):
content = [Span(task_object.about)]
super().__init__(content, _id=f'task_{task_object.id}')
class Tasklist(Ul):
def __init__(self):
self.subscriptions = [('tasks', ('todos.components.base', 'Task'))]
content = [
Task(t)
for t in Tasks.objects.filter()
]
super().__init__(content, _id='tasklist')
class Base(Div):
def __init__(self):
content = [Tasklist()]
super().__init__(content, _id='tasklist_container')
import importlib
import json
from channels.generic.websocket import JsonWebsocketConsumer
from channels.auth import get_user
from django.contrib.auth.models import User
from asgiref.sync import async_to_sync
from todos.models import Clients, Subscriptions
class Consumer(JsonWebsocketConsumer, object):
def connect(self):
self.accept()
user = async_to_sync(get_user)(self.scope)
Clients.objects.create(
channel=self.channel_name,
user=user if isinstance(user, User) else None
)
self.send(json.dumps({'type': 'Connected'}))
def disconnect(self, close_code):
# Note that in some rare cases (power loss, etc) disconnect may fail
# to run; this naive example would leave zombie channel names around.
print('Disconnect')
Clients.objects.filter(channel=self.channel_name).delete()
def receive(self, text_data):
data = json.loads(text_data)
msg_type = None
if not data.get('_id', None):
return
try:
msg_type = data['type']
except KeyError:
self.send(json.dumps({
'_id': data['_id'],
'type': 'Error',
'params': {
'name': 'Bad message',
'message': 'message type not found'
}
}))
return
if msg_type in ['subscribe', 'unsubscribe', 'method', 'geturl']:
func = getattr(self, f'recv_{msg_type}', None)
if func:
try:
func(data)
except KeyError as e:
print(e)
self.send(json.dumps({
'_id': data.get('_id'),
'type': 'Error',
'params': {
'name': 'Bad format',
'message': '"params" key not found'
}
}))
else:
self.send(json.dumps({
'_id': data['_id'],
'type': 'Error',
'params': {
'name': 'Bad message type',
'message': f'{msg_type} not recognized'
}
}))
def recv_geturl(self, data):
base = importlib.import_module('.base', 'todos.components')
data = {
'_id': data['_id'],
'type': 'Result',
'params': base.Base().to_obj()
}
self.send(json.dumps(data))
def recv_getcomponent(self, data):
pass
def insert_component(self, data, change=False):
self.send(json.dumps({
'type': 'DDP',
'params': {
'type': 'insert' if not change else 'change',
'params': data['instance']
}
}))
def remove_component(self, data):
self.send(json.dumps({
'type': 'DDP',
'params': {
'type': 'remove',
'params': {
'_id': data['_id'],
'parent': data['parent']
}
}
}))
def handle_ddp(self, data):
if data['params']['type'] == 'inserted':
self.insert_component(data['params'])
elif data['params']['type'] == 'changed':
self.insert_component(data['params'], True)
elif data['params']['type'] == 'removed':
self.remove_component(data['params'])
def recv_subscribe(self, data):
params = data['params']
to_send = {'_id': data['_id']}
client = Clients.objects.get(channel=self.channel_name)
for key in ['name', '_id', 'template']:
if key not in params:
to_send.update({
'type': 'Error',
'params': {
'name': 'Bad format',
'message': f'Subscription {key} not found'
}
})
self.send(json.dumps(to_send))
return
if not client:
to_send.update({
'type': 'Error',
'params': {
'name': 'Client not found',
'message': 'No client was found for this channel name'
}
})
else:
sub = Subscriptions.objects.create(
name=params['name'],
parent=params['_id'],
template_module=params['template'][0],
template_class=params['template'][1],
client=client)
to_send.update({
'type': 'Result',
'params': {
'name': params['name'],
'sub_id': sub.id
}
})
self.send(json.dumps(to_send))
def recv_unsubscribe(self, data):
params = data['params']
self.send(json.dumps({
'_id': data['_id'],
'type': 'unsubscribed',
'message': 'Got unsub',
'params': {
'name': params['name']
}
}))
# Generated by Django 2.1.7 on 2019-02-23 10:29
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
initial = True
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.CreateModel(
name='Clients',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('channel', models.CharField(max_length=255)),
('user', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL)),
],
),
migrations.CreateModel(
name='Subscriptions',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=255)),
('client', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='todos.Clients')),
],
),
]
# Generated by Django 2.1.7 on 2019-02-24 15:44
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('todos', '0001_initial'),
]
operations = [
migrations.CreateModel(
name='Tasks',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('about', models.CharField(max_length=1024)),
('user', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL)),
],
),
]
# Generated by Django 2.1.7 on 2019-02-24 19:03
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('todos', '0002_tasks'),
]
operations = [
migrations.AddField(
model_name='subscriptions',
name='parent',
field=models.CharField(default='body', max_length=255),
preserve_default=False,
),
]
# Generated by Django 2.1.7 on 2019-02-25 04:46
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('todos', '0003_subscriptions_parent'),
]
operations = [
migrations.AddField(
model_name='subscriptions',
name='template_class',
field=models.CharField(default='Task', max_length=255),
preserve_default=False,
),
migrations.AddField(
model_name='subscriptions',
name='template_module',
field=models.CharField(default='todos.components.base', max_length=255),
preserve_default=False,
),
]
import importlib
from django.db import models
from django.contrib.auth.models import User
from django.db.models.signals import post_delete, post_save
from django.dispatch import receiver
from asgiref.sync import async_to_sync
from channels.layers import get_channel_layer
class Clients(models.Model):
channel = models.CharField(max_length=255)
user = models.ForeignKey(
User,
models.SET_NULL,
blank=True,
null=True
)
class Subscriptions(models.Model):
name = models.CharField(max_length=255)
parent = models.CharField(max_length=255)
template_module = models.CharField(max_length=255)
template_class = models.CharField(max_length=255)
client = models.ForeignKey(Clients, models.CASCADE)
class Tasks(models.Model):
user = models.ForeignKey(User, models.SET_NULL, blank=True, null=True)
about = models.CharField(max_length=1024)
@receiver(post_save, sender=Tasks)
def ddp_insert_change(sender, **kwargs):
created = kwargs.pop('created')
instance = kwargs.pop('instance')
data = {
'type': 'handle.ddp',
'params': {
'type': 'inserted' if created else 'changed',
}
}
subs = Subscriptions.objects.filter(name='tasks')
for sub in subs:
mfile, mpath = sub.template_module[::-1].split('.', 1)
tmpl_module = importlib.import_module(f'.{mfile[::-1]}', mpath[::-1])
tmpl_class = getattr(tmpl_module, sub.template_class)
tmpl_instance = tmpl_class(instance)
tmpl_instance.parent = sub.parent
data['params']['instance'] = tmpl_instance.to_obj()
client = sub.client
channel = get_channel_layer()
async_to_sync(channel.send)(client.channel, data)
@receiver(post_delete, sender=Tasks)
def ddp_delete(sender, **kwargs):
instance =