Skip to content
Snippets Groups Projects
Verified Commit 73352316 authored by Tom Teichler's avatar Tom Teichler :beers:
Browse files

Merge branch 'master' into test

parents 2f6fb73b 0e336bcb
No related branches found
No related tags found
1 merge request!531[CI] Enable review apps
Pipeline #6472 waiting for manual action
......@@ -31,6 +31,7 @@ RUN apt-get -y update && \
libpq-dev \
libssl-dev \
netcat-openbsd \
postgresql-client \
yarnpkg && \
eatmydata pip install uwsgi django-compressor
......
from datetime import datetime, time
from typing import Callable, Dict, List, Sequence, Tuple
from django import forms
from django.contrib.auth import get_user_model
from django.core.exceptions import ValidationError
from django.db.models import QuerySet
from django.http import HttpRequest
from django.utils.translation import gettext_lazy as _
from django_select2.forms import ModelSelect2MultipleWidget, ModelSelect2Widget, Select2Widget
......@@ -370,3 +373,144 @@ class DashboardWidgetOrderForm(ExtensibleForm):
DashboardWidgetOrderFormSet = forms.formset_factory(
form=DashboardWidgetOrderForm, max_num=0, extra=0
)
class ActionForm(forms.Form):
"""Generic form for executing actions on multiple items of a queryset.
This should be used together with a ``Table`` from django-tables2
which includes a ``SelectColumn``.
The queryset can be defined in two different ways:
You can use ``get_queryset`` or provide ``queryset`` as keyword argument
at the initialization of this form class.
If both are declared, it will use the keyword argument.
Any actions can be defined using the ``actions`` class attribute
or overriding the method ``get_actions``.
The actions use the same syntax like the Django Admin actions with one important difference:
Instead of the related model admin,
these actions will get the related ``ActionForm`` as first argument.
Here you can see an example for such an action:
.. code-block:: python
from django.utils.translation import gettext as _
def example_action(form, request, queryset):
# Do something with this queryset
example_action.short_description = _("Example action")
If you can include the ``ActionForm`` like any other form in your views,
but you must add the request as first argument.
When the form is valid, you should run ``execute``:
.. code-block:: python
from aleksis.core.forms import ActionForm
def your_view(request, ...):
# Something
action_form = ActionForm(request, request.POST or None, ...)
if request.method == "POST" and form.is_valid():
form.execute()
# Something
"""
layout = Layout("action")
actions = []
def get_actions(self) -> Sequence[Callable]:
"""Get all defined actions."""
return self.actions
def _get_actions_dict(self) -> Dict[str, Callable]:
"""Get all defined actions as dictionary."""
return {value.__name__: value for value in self.get_actions()}
def _get_action_choices(self) -> List[Tuple[str, str]]:
"""Get all defined actions as Django choices."""
return [
(value.__name__, getattr(value, "short_description", value.__name__))
for value in self.get_actions()
]
def get_queryset(self) -> QuerySet:
"""Get the related queryset."""
raise NotImplementedError("Queryset necessary.")
action = forms.ChoiceField(choices=[])
selected_objects = forms.ModelMultipleChoiceField(queryset=None)
def __init__(self, request: HttpRequest, *args, queryset: QuerySet = None, **kwargs):
self.request = request
self.queryset = queryset if isinstance(queryset, QuerySet) else self.get_queryset()
super().__init__(*args, **kwargs)
self.fields["selected_objects"].queryset = self.queryset
self.fields["action"].choices = self._get_action_choices()
def execute(self) -> bool:
"""Execute the selected action on all selected objects.
:return: If the form is not valid, it will return ``False``.
"""
if self.is_valid():
data = self.cleaned_data["selected_objects"]
action = self._get_actions_dict()[self.cleaned_data["action"]]
action(None, self.request, data)
return True
return False
class ListActionForm(ActionForm):
"""Generic form for executing actions on multiple items of a list of dictionaries.
Sometimes you want to implement actions for data from different sources
than querysets or even querysets from multiple models. For these cases,
you can use this form.
To provide an unique identification of each item, the dictionaries **must**
include the attribute ``pk``. This attribute has to be unique for the whole list.
If you don't mind this aspect, this will cause unexpected behavior.
Any actions can be defined as described in ``ActionForm``, but, of course,
the last argument won't be a queryset but a list of dictionaries.
For further information on usage, you can take a look at ``ActionForm``.
"""
selected_objects = forms.MultipleChoiceField(choices=[])
def get_queryset(self):
# Return None in order not to raise an unwanted exception
return None
def _get_dict(self) -> Dict[str, dict]:
"""Get the items sorted by pk attribute."""
return {item["pk"]: item for item in self.items}
def _get_choices(self) -> List[Tuple[str, str]]:
"""Get the items as Django choices."""
return [(item["pk"], item["pk"]) for item in self.items]
def _get_real_items(self, items: Sequence[dict]) -> List[dict]:
"""Get the real dictionaries from a list of pks."""
items_dict = self._get_dict()
real_items = []
for item in items:
if item not in items_dict:
raise ValidationError(_("No valid selection."))
real_items.append(items_dict[item])
return real_items
def clean_selected_objects(self) -> List[dict]:
data = self.cleaned_data["selected_objects"]
items = self._get_real_items(data)
return items
def __init__(self, request: HttpRequest, items, *args, **kwargs):
self.items = items
super().__init__(request, *args, **kwargs)
self.fields["selected_objects"].choices = self._get_choices()
$(document).ready(function () {
$(".select--header-box").change(function () {
/*
If the top checkbox is checked, all sub checkboxes should be checked,
if it gets unchecked, all other ones should get unchecked.
*/
if ($(this).is(":checked")) {
$(this).closest("table").find('input[name="selected_objects"]').prop({
indeterminate: false,
checked: true,
});
} else {
$(this).closest("table").find('input[name="selected_objects"]').prop({
indeterminate: false,
checked: false,
});
}
});
$('input[name="selected_objects"]').change(function () {
/*
If a table checkbox changes, check the state of the other ones.
If all boxes are checked the box in the header should be checked,
if all boxes are unchecked the header box should be unchecked. If
only some boxes are checked the top one should be inderteminate.
*/
let checked = $(this).is(":checked");
let indeterminate = false;
let table = $(this).closest("table");
table.find('input[name="selected_objects"]').each(function () {
if ($(this).is(":checked") !== checked) {
/* Set the header box to indeterminate if the boxes are not the same */
table.find(".select--header-box").prop({
indeterminate: true,
})
indeterminate = true;
return false;
}
});
if (!(indeterminate)) {
/* All boxes are the same, set the header box to the same value */
table.find(".select--header-box").prop({
indeterminate: false,
checked: checked,
});
}
});
});
from django.utils.safestring import mark_safe
from django_tables2 import CheckBoxColumn
from django_tables2.utils import A, AttributeDict, computed_values
class MaterializeCheckboxColumn(CheckBoxColumn):
"""Checkbox column with Materialize support."""
empty_values = ()
@property
def header(self):
"""Render the header cell."""
default = {"type": "checkbox"}
general = self.attrs.get("input")
specific = self.attrs.get("th__input")
attrs = AttributeDict(default, **(specific or general or {}))
return mark_safe("<label><input %s/><span></span></label>" % attrs.as_html()) # noqa
def render(self, value, bound_column, record):
"""Render a data cell."""
default = {"type": "checkbox", "name": bound_column.name, "value": value}
if self.is_checked(value, record):
default.update({"checked": "checked"})
general = self.attrs.get("input")
specific = self.attrs.get("td__input")
attrs = dict(default, **(specific or general or {}))
attrs = computed_values(attrs, kwargs={"record": record, "value": value})
return mark_safe( # noqa
"<label><input %s/><span></span</label>" % AttributeDict(attrs).as_html()
)
class SelectColumn(MaterializeCheckboxColumn):
"""Column with a check box prepared for `ActionForm` forms."""
def __init__(self, *args, **kwargs):
kwargs["attrs"] = {
"td__input": {"name": "selected_objects"},
"th__input": {"class": "select--header-box"},
}
kwargs.setdefault("accessor", A("pk"))
super().__init__(*args, **kwargs)
#!/bin/bash
HTTP_PORT=${HTTP_PORT:8000}
export ALEKSIS_database__host=${ALEKSIS_database__host:-127.0.0.1}
export ALEKSIS_database__port=${ALEKSIS_database__port:-5432}
HTTP_PORT=${HTTP_PORT:-8000}
RUN_MODE=uwsgi
if [[ -z $ALEKSIS_secret_key ]]; then
if [[ ! -e /var/lib/aleksis/secret_key ]]; then
......@@ -13,13 +11,34 @@ if [[ -z $ALEKSIS_secret_key ]]; then
ALEKSIS_secret_key=$(</var/lib/aleksis/secret_key)
fi
while ! nc -z $ALEKSIS_database__host $ALEKSIS_database__port; do
sleep 0.1
echo -n "Waiting for database."
while ! aleksis-admin dbshell -- -c "SELECT 1" >/dev/null 2>&1; do
sleep 0.5
echo -n .
done
echo
aleksis-admin migrate
aleksis-admin createinitialrevisions
aleksis-admin compilescss
aleksis-admin collectstatic --no-input --clear
exec aleksis-admin runuwsgi -- --http-socket=:$HTTP_PORT
case "$RUN_MODE" in
uwsgi)
aleksis-admin migrate
aleksis-admin createinitialrevisions
aleksis-admin compilescss
aleksis-admin collectstatic --no-input --clear
exec aleksis-admin runuwsgi -- --http-socket=:$HTTP_PORT
;;
celery-worker)
aleksis-admin migrate
aleksis-admin createinitialrevisions
exec celery -A aleksis.core worker
;;
celery-beat)
aleksis-admin migrate
exec celery -A aleksis.core beat
;;
*)
exec "$@"
;;
esac
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment