diff --git a/mixins/__init__.py b/mixins/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/mixins/__main__.py b/mixins/__main__.py
new file mode 100644
index 0000000..e69de29
diff --git a/mixins/restrictions.py b/mixins/restrictions.py
new file mode 100644
index 0000000..26666d2
--- /dev/null
+++ b/mixins/restrictions.py
@@ -0,0 +1,100 @@
+from django.core.exceptions import FieldDoesNotExist, ImproperlyConfigured
+from django.core.paginator import Paginator
+from django.db.models import QuerySet
+
+
+class RestrictedViewMixin:
+ """
+ This mixin overrides two helpers in order to pass the user object to the filters.
+ get_queryset alters the objects returned for list views.
+ get_form_kwargs passes the request object to the form class. Remaining permissions
+ checks are in forms.py
+ """
+
+ allow_empty = True
+ queryset = None
+ model = None
+ paginate_by = None
+ paginate_orphans = 0
+ context_object_name = None
+ paginator_class = Paginator
+ page_kwarg = "page"
+ ordering = None
+
+ def set_extra_args(self, user):
+ """
+ This function is overriden to filter the objects by the requesting user.
+ """
+ self.extra_permission_args = {}
+
+ def get_queryset(self, **kwargs):
+ """
+ This function is overriden to filter the objects by the requesting user.
+ """
+ self.set_extra_args(self.request.user)
+ if self.queryset is not None:
+ queryset = self.queryset
+ if isinstance(queryset, QuerySet):
+ # queryset = queryset.all()
+ queryset = queryset.filter(
+ user=self.request.user, **self.extra_permission_args
+ )
+ elif self.model is not None:
+ queryset = self.model._default_manager.filter(
+ user=self.request.user, **self.extra_permission_args
+ )
+ else:
+ raise ImproperlyConfigured(
+ "%(cls)s is missing a QuerySet. Define "
+ "%(cls)s.model, %(cls)s.queryset, or override "
+ "%(cls)s.get_queryset()." % {"cls": self.__class__.__name__}
+ )
+ if hasattr(self, "get_ordering"):
+ ordering = self.get_ordering()
+ if ordering:
+ if isinstance(ordering, str):
+ ordering = (ordering,)
+ queryset = queryset.order_by(*ordering)
+
+ return queryset
+
+ def get_form_kwargs(self):
+ """Passes the request object to the form class.
+ This is necessary to only display members that belong to a given user"""
+
+ kwargs = super().get_form_kwargs()
+ kwargs["request"] = self.request
+ return kwargs
+
+
+class RestrictedFormMixin:
+ """
+ This mixin is used to restrict the queryset of a form to the current user.
+ The request object is passed from the view.
+ Fieldargs is used to pass additional arguments to the queryset filter.
+ """
+
+ fieldargs = {}
+
+ # TODO: implement set_extra_args to check more permissions here
+ # for completeness, however as views open forms, the permissions
+ # are already checked there, so it may not be necessary.
+ def __init__(self, *args, **kwargs):
+ # self.fieldargs = {}
+ self.request = kwargs.pop("request")
+ super().__init__(*args, **kwargs)
+ for field in self.fields:
+ # Check it's not something like a CharField which has no queryset
+ if not hasattr(self.fields[field], "queryset"):
+ continue
+
+ model = self.fields[field].queryset.model
+ # Check if the model has a user field
+ try:
+ model._meta.get_field("user")
+ # Add the user to the queryset filters
+ self.fields[field].queryset = model.objects.filter(
+ user=self.request.user, **self.fieldargs.get(field, {})
+ )
+ except FieldDoesNotExist:
+ pass
diff --git a/mixins/static/modal.js b/mixins/static/modal.js
new file mode 100644
index 0000000..0535526
--- /dev/null
+++ b/mixins/static/modal.js
@@ -0,0 +1,44 @@
+// var modal = document.querySelector('.modal'); // assuming you have only 1
+var modal = document.getElementById("modal");
+var html = document.querySelector('html');
+
+var disableModal = function() {
+ modal.classList.remove('is-active');
+ html.classList.remove('is-clipped');
+ var modal_refresh = document.getElementsByClassName("modal-refresh");
+ for(var i = 0; i < modal_refresh.length; i++) {
+ modal_refresh[i].remove();
+ }
+}
+
+var elements = document.querySelectorAll('.modal-background');
+for(var i = 0; i < elements.length; i++) {
+ elements[i].addEventListener('click', function(e) {
+ // elements[i].preventDefault();
+ disableModal();
+ });
+}
+
+var elements = document.querySelectorAll('.modal-close');
+for(var i = 0; i < elements.length; i++) {
+ elements[i].addEventListener('click', function(e) {
+ // elements[i].preventDefault();
+ disableModal();
+ });
+}
+
+function activateButtons() {
+ var elements = document.querySelectorAll('.modal-close-button');
+ for(var i = 0; i < elements.length; i++) {
+ elements[i].addEventListener('click', function(e) {
+ // elements[i].preventDefault();
+ disableModal();
+ });
+ }
+}
+activateButtons();
+// modal.querySelector('.modal-close-button').addEventListener('click', function(e) {
+// e.preventDefault();
+// modal.classList.remove('is-active');
+// html.classList.remove('is-clipped');
+// });
diff --git a/mixins/templates/mixins/partials/close-modal.html b/mixins/templates/mixins/partials/close-modal.html
new file mode 100644
index 0000000..6c0173c
--- /dev/null
+++ b/mixins/templates/mixins/partials/close-modal.html
@@ -0,0 +1 @@
+
diff --git a/mixins/templates/mixins/partials/close-widget.html b/mixins/templates/mixins/partials/close-widget.html
new file mode 100644
index 0000000..5c6f6ea
--- /dev/null
+++ b/mixins/templates/mixins/partials/close-widget.html
@@ -0,0 +1,3 @@
+
\ No newline at end of file
diff --git a/mixins/templates/mixins/partials/close-window.html b/mixins/templates/mixins/partials/close-window.html
new file mode 100644
index 0000000..894974c
--- /dev/null
+++ b/mixins/templates/mixins/partials/close-window.html
@@ -0,0 +1,3 @@
+
\ No newline at end of file
diff --git a/mixins/templates/mixins/partials/generic-detail.html b/mixins/templates/mixins/partials/generic-detail.html
new file mode 100644
index 0000000..3eee173
--- /dev/null
+++ b/mixins/templates/mixins/partials/generic-detail.html
@@ -0,0 +1,72 @@
+{% load pretty %}
+{% include 'partials/notify.html' %}
+
+{% if live is not None %}
+
Live {{ context_object_name_singular }} info
+
+
+
attribute
+
value
+
+
+ {% block live_tbody %}
+ {% for key, item in live.items %}
+ {% if key in pretty %}
+
+
{{ key }}
+
+ {% if item is not None %}
+
{{ item|pretty }}
+ {% endif %}
+
+
+ {% else %}
+
+
{{ key }}
+
+ {% if item is not None %}
+ {{ item }}
+ {% endif %}
+
+
+ {% endif %}
+ {% endfor %}
+ {% endblock %}
+
+
+{% endif %}
+
+{% if object is not None %}
+
{{ title_singular }} info
+
+
+
attribute
+
value
+
+
+ {% block tbody %}
+ {% for key, item in object.items %}
+ {% if key in pretty %}
+
+
{{ key }}
+
+ {% if item is not None %}
+
{{ item|pretty }}
+ {% endif %}
+
+
+ {% else %}
+
+
{{ key }}
+
+ {% if item is not None %}
+ {{ item }}
+ {% endif %}
+
+
+ {% endif %}
+ {% endfor %}
+ {% endblock %}
+
+
+{% endif %}
\ No newline at end of file
diff --git a/mixins/templates/mixins/partials/notify.html b/mixins/templates/mixins/partials/notify.html
new file mode 100644
index 0000000..fcf970b
--- /dev/null
+++ b/mixins/templates/mixins/partials/notify.html
@@ -0,0 +1,7 @@
+
+ {% if message is not None %}
+
+ {{ message }}
+
+ {% endif %}
+
\ No newline at end of file
diff --git a/mixins/templates/mixins/window-content/object-form.html b/mixins/templates/mixins/window-content/object-form.html
new file mode 100644
index 0000000..bdfb43a
--- /dev/null
+++ b/mixins/templates/mixins/window-content/object-form.html
@@ -0,0 +1,34 @@
+{% include 'partials/notify.html' %}
+{% if page_title is not None %}
+
{{ page_title }}
+{% endif %}
+{% if page_subtitle is not None %}
+
{{ page_subtitle }}
+{% endif %}
+{% load crispy_forms_tags %}
+
+{% load crispy_forms_bulma_field %}
+
+
+
+
+
+
+
+
+
diff --git a/mixins/templates/mixins/window-content/object.html b/mixins/templates/mixins/window-content/object.html
new file mode 100644
index 0000000..69b4f3c
--- /dev/null
+++ b/mixins/templates/mixins/window-content/object.html
@@ -0,0 +1,45 @@
+{% include 'partials/notify.html' %}
+{% if page_title is not None %}
+
{{ page_title }}
+{% endif %}
+{% if page_subtitle is not None %}
+
{{ page_subtitle }}
+{% endif %}
+
+
+ {% if submit_url is not None %}
+
+ {% endif %}
+ {% if delete_all_url is not None %}
+
+ {% endif %}
+
+
+{% include detail_template %}
+
diff --git a/mixins/templates/mixins/window-content/objects.html b/mixins/templates/mixins/window-content/objects.html
new file mode 100644
index 0000000..d7a2e9b
--- /dev/null
+++ b/mixins/templates/mixins/window-content/objects.html
@@ -0,0 +1,45 @@
+{% include 'partials/notify.html' %}
+{% if page_title is not None %}
+
{{ page_title }}
+{% endif %}
+{% if page_subtitle is not None %}
+
{{ page_subtitle }}
+{% endif %}
+
+
+ {% if submit_url is not None %}
+
+ {% endif %}
+ {% if delete_all_url is not None %}
+
+ {% endif %}
+