Compare commits
No commits in common. "a2a399e531f1bd32009960aece157bed4556816d" and "07d72d799d4247f53ce17799b6af0cf75333d2f1" have entirely different histories.
a2a399e531
...
07d72d799d
|
@ -1,7 +1,5 @@
|
||||||
from django.contrib.auth.models import User
|
|
||||||
from django.db.models import Q
|
|
||||||
from django.utils.translation import gettext_lazy as _
|
|
||||||
import django_filters as filters
|
import django_filters as filters
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
from . import models
|
from . import models
|
||||||
|
|
||||||
|
@ -12,8 +10,6 @@ __all__ = (
|
||||||
"ImageFilterSet",
|
"ImageFilterSet",
|
||||||
"SoundFilterSet",
|
"SoundFilterSet",
|
||||||
"TrackFilterSet",
|
"TrackFilterSet",
|
||||||
"UserFilterSet",
|
|
||||||
"UserGroupFilterSet",
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -71,26 +67,3 @@ class TrackFilterSet(filters.FilterSet):
|
||||||
artist = filters.CharFilter(field_name="artist", lookup_expr="icontains")
|
artist = filters.CharFilter(field_name="artist", lookup_expr="icontains")
|
||||||
album = filters.CharFilter(field_name="album", lookup_expr="icontains")
|
album = filters.CharFilter(field_name="album", lookup_expr="icontains")
|
||||||
title = filters.CharFilter(field_name="title", lookup_expr="icontains")
|
title = filters.CharFilter(field_name="title", lookup_expr="icontains")
|
||||||
|
|
||||||
|
|
||||||
class UserFilterSet(filters.FilterSet):
|
|
||||||
search = filters.CharFilter(field_name="search", method="search_filter")
|
|
||||||
in_group = filters.NumberFilter(field_name="in_group", method="in_group_filter")
|
|
||||||
not_in_group = filters.NumberFilter(field_name="not_in_group", method="not_in_group_filter")
|
|
||||||
|
|
||||||
def in_group_filter(self, queryset, name, value):
|
|
||||||
return queryset.filter(groups__in=[value])
|
|
||||||
|
|
||||||
def not_in_group_filter(self, queryset, name, value):
|
|
||||||
return queryset.exclude(groups__in=[value])
|
|
||||||
|
|
||||||
def search_filter(self, queryset, name, value):
|
|
||||||
return queryset.filter(
|
|
||||||
Q(username__icontains=value) | Q(first_name__icontains=value) | Q(last_name__icontains=value)
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class UserGroupFilterSet(filters.FilterSet):
|
|
||||||
class Meta:
|
|
||||||
model = User.groups.through
|
|
||||||
fields = ["group", "user"]
|
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
from . import widgets
|
from . import widgets
|
||||||
|
|
||||||
|
from .auth import UserGroupFormSet
|
||||||
from .episode import EpisodeForm, EpisodeSoundFormSet
|
from .episode import EpisodeForm, EpisodeSoundFormSet
|
||||||
from .program import ProgramForm
|
from .program import ProgramForm
|
||||||
from .page import CommentForm, ImageForm, PageForm, ChildPageForm
|
from .page import CommentForm, ImageForm, PageForm, ChildPageForm
|
||||||
|
@ -18,4 +19,5 @@ __all__ = (
|
||||||
ChildPageForm,
|
ChildPageForm,
|
||||||
SoundForm,
|
SoundForm,
|
||||||
SoundCreateForm,
|
SoundCreateForm,
|
||||||
|
UserGroupFormSet,
|
||||||
)
|
)
|
||||||
|
|
20
aircox/forms/auth.py
Normal file
20
aircox/forms/auth.py
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
from django import forms
|
||||||
|
from django.forms.models import modelformset_factory
|
||||||
|
from django.contrib.auth.models import User
|
||||||
|
|
||||||
|
from aircox.forms import widgets
|
||||||
|
|
||||||
|
|
||||||
|
__all__ = ("UserGroupFormSet",)
|
||||||
|
|
||||||
|
|
||||||
|
UserGroupFormSet = modelformset_factory(
|
||||||
|
User.groups.through,
|
||||||
|
fields=("group", "user"),
|
||||||
|
widgets={
|
||||||
|
"group": forms.HiddenInput(),
|
||||||
|
"user": widgets.VueAutoComplete("api:usergroup-autocomplete", lookup="username"),
|
||||||
|
},
|
||||||
|
extra=0,
|
||||||
|
can_delete=True,
|
||||||
|
)
|
12
aircox/static/aircox/admin.html
Normal file
12
aircox/static/aircox/admin.html
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
|
<meta name="viewport" content="width=device-width,initial-scale=1.0">
|
||||||
|
<title>Vue App</title>
|
||||||
|
<script defer src="js/chunk-vendors.js"></script><script defer src="js/chunk-common.js"></script><script defer src="js/admin.js"></script><link href="css/chunk-vendors.css" rel="stylesheet"><link href="css/chunk-common.css" rel="stylesheet"></head>
|
||||||
|
<body>
|
||||||
|
<div id="app"></div>
|
||||||
|
</body>
|
||||||
|
</html>
|
9959
aircox/static/aircox/css/chunk-common.css
Normal file
9959
aircox/static/aircox/css/chunk-common.css
Normal file
File diff suppressed because it is too large
Load Diff
9143
aircox/static/aircox/css/chunk-vendors.css
Normal file
9143
aircox/static/aircox/css/chunk-vendors.css
Normal file
File diff suppressed because one or more lines are too long
1016
aircox/static/aircox/css/public.css
Normal file
1016
aircox/static/aircox/css/public.css
Normal file
File diff suppressed because it is too large
Load Diff
BIN
aircox/static/aircox/fonts/fa-brands-400.ttf
Normal file
BIN
aircox/static/aircox/fonts/fa-brands-400.ttf
Normal file
Binary file not shown.
BIN
aircox/static/aircox/fonts/fa-brands-400.woff2
Normal file
BIN
aircox/static/aircox/fonts/fa-brands-400.woff2
Normal file
Binary file not shown.
BIN
aircox/static/aircox/fonts/fa-regular-400.ttf
Normal file
BIN
aircox/static/aircox/fonts/fa-regular-400.ttf
Normal file
Binary file not shown.
BIN
aircox/static/aircox/fonts/fa-regular-400.woff2
Normal file
BIN
aircox/static/aircox/fonts/fa-regular-400.woff2
Normal file
Binary file not shown.
BIN
aircox/static/aircox/fonts/fa-solid-900.ttf
Normal file
BIN
aircox/static/aircox/fonts/fa-solid-900.ttf
Normal file
Binary file not shown.
BIN
aircox/static/aircox/fonts/fa-solid-900.woff2
Normal file
BIN
aircox/static/aircox/fonts/fa-solid-900.woff2
Normal file
Binary file not shown.
BIN
aircox/static/aircox/fonts/fa-v4compatibility.ttf
Normal file
BIN
aircox/static/aircox/fonts/fa-v4compatibility.ttf
Normal file
Binary file not shown.
|
@ -19,21 +19,12 @@ Usefull context:
|
||||||
<link rel="icon" href="{% thumbnail site.favicon 32x32 crop %}" />
|
<link rel="icon" href="{% thumbnail site.favicon 32x32 crop %}" />
|
||||||
|
|
||||||
{% block assets %}
|
{% block assets %}
|
||||||
{% static "vue/vue.esm-browser.js" as vue_url %}
|
<link rel="stylesheet" type="text/css" href="{% static "aircox/css/chunk-common.css" %}"/>
|
||||||
<script type="importmap">
|
<link rel="stylesheet" type="text/css" href="{% static "aircox/css/chunk-vendors.css" %}"/>
|
||||||
{
|
<link rel="stylesheet" type="text/css" href="{% static "aircox/css/public.css" %}"/>
|
||||||
"imports": {
|
<script src="{% static "aircox/js/chunk-common.js" %}"></script>
|
||||||
"vue": "{{vue_url}}"
|
<script src="{% static "aircox/js/chunk-vendors.js" %}"></script>
|
||||||
}
|
<script src="{% static "aircox/js/public.js" %}"></script>
|
||||||
}
|
|
||||||
</script>
|
|
||||||
<script type="module" src="{{vue_url}}"></script>
|
|
||||||
|
|
||||||
<link rel="stylesheet" type="text/css" href="{% static "fontawesome-free/css/all.min.css" %}"/>
|
|
||||||
<link rel="stylesheet" type="text/css" href="{% static "aircox/index.css" %}"/>
|
|
||||||
<link rel="stylesheet" type="text/css" href="{% static "aircox/public.css" %}"/>
|
|
||||||
|
|
||||||
<script type="module" src="{% if app_js_url %}{{ app_js_url }}{% else %}{% static "aircox/public.js" %}{% endif %}"></script>
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
<title>
|
<title>
|
||||||
|
@ -56,7 +47,6 @@ Usefull context:
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
<div id="app">
|
<div id="app">
|
||||||
{% block app %}
|
|
||||||
<div class="navs">
|
<div class="navs">
|
||||||
{% block nav %}
|
{% block nav %}
|
||||||
<nav class="nav primary" role="navigation" aria-label="main navigation">
|
<nav class="nav primary" role="navigation" aria-label="main navigation">
|
||||||
|
@ -168,7 +158,6 @@ Usefull context:
|
||||||
<div id="player">{% include "aircox/widgets/player.html" %}</div>
|
<div id="player">{% include "aircox/widgets/player.html" %}</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% endblock %}
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
@ -2,9 +2,9 @@
|
||||||
{% load static i18n %}
|
{% load static i18n %}
|
||||||
|
|
||||||
{% block assets %}
|
{% block assets %}
|
||||||
{% static "aircox/admin.js" as app_js_url %}
|
|
||||||
{{ block.super }}
|
{{ block.super }}
|
||||||
<link rel="stylesheet" type="text/css" href="{% static "aircox/admin.css" %}"/>
|
<script src="{% static "aircox/js/dashboard.js" %}"></script>
|
||||||
|
<link rel="stylesheet" type="text/css" href="{% static "aircox/css/admin.css" %}"/>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block head-title %}
|
{% block head-title %}
|
||||||
|
|
|
@ -1,18 +0,0 @@
|
||||||
{% load i18n %}
|
|
||||||
|
|
||||||
<a-modal ref="group-users-modal">
|
|
||||||
<template #title="{item}">[[ item?.name ]]</template>
|
|
||||||
<template #default="{item}">
|
|
||||||
<a-group-users v-if="item" ref="group-users"
|
|
||||||
:url="'{% url 'api:usergroup-list' %}?group=' + item.id"
|
|
||||||
commit-url="{% url 'api:usergroup-commit' %}"
|
|
||||||
:search-url="'{% url 'api:user-autocomplete' %}?search=${query}&group=' + item.id"
|
|
||||||
:initials="{group_id: item.id }"
|
|
||||||
/>
|
|
||||||
</template>
|
|
||||||
<template #footer="{item, close}">
|
|
||||||
<button type="button" class="button" @click="$refs['group-users'].save(); close()">
|
|
||||||
Save
|
|
||||||
</button>
|
|
||||||
</template>
|
|
||||||
</a-modal>
|
|
|
@ -18,10 +18,11 @@ Context:
|
||||||
{{ formset.non_form_errors }}
|
{{ formset.non_form_errors }}
|
||||||
<!-- formset.management_form -->
|
<!-- formset.management_form -->
|
||||||
|
|
||||||
<{{ tag|default:"a-form-set" }} ref="formset"
|
<{{ tag|default:"a-form-set" }}
|
||||||
{% block tag-attrs %}
|
{% block tag-attrs %}
|
||||||
:form-data="{{ formset_data|json }}"
|
:form-data="{{ formset_data|json }}"
|
||||||
:labels="window.aircox.labels"
|
:labels="window.aircox.labels"
|
||||||
|
:init-data="{% formset_inline_data formset=formset %}"
|
||||||
:columns="[{% for n, f in fields.items %}{% if not f.widget.is_hidden %}'{{ n }}',{% endif %}{% endfor %} ]"
|
:columns="[{% for n, f in fields.items %}{% if not f.widget.is_hidden %}'{{ n }}',{% endif %}{% endfor %} ]"
|
||||||
settings-url="{% url "api:user-settings" %}"
|
settings-url="{% url "api:user-settings" %}"
|
||||||
data-prefix="{{ formset.prefix }}-"
|
data-prefix="{{ formset.prefix }}-"
|
||||||
|
@ -39,7 +40,7 @@ Context:
|
||||||
</template>
|
</template>
|
||||||
{% for name, field in fields.items %}
|
{% for name, field in fields.items %}
|
||||||
{% if not field.widget.is_hidden and not field.is_readonly %}
|
{% if not field.widget.is_hidden and not field.is_readonly %}
|
||||||
<template v-slot:control-{{ name }}="{context,item,cell,value,attr,emit,inputName}">
|
<template v-slot:control-{{ name }}="{item,cell,value,attr,emit,inputName}">
|
||||||
{% block row-control %}
|
{% block row-control %}
|
||||||
{% include "./v_form_field.html" with value="item.data."|add:name name="inputName" %}
|
{% include "./v_form_field.html" with value="item.data."|add:name name="inputName" %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
{% extends "./public.html" %}
|
{% extends "./public.html" %}
|
||||||
{% load i18n aircox %}
|
{% load i18n aircox %}
|
||||||
|
|
||||||
|
|
||||||
{% block head_title %}{{ station.name }}{% endblock %}
|
{% block head_title %}{{ station.name }}{% endblock %}
|
||||||
|
|
||||||
{% block title %}{% if page %}{{ block.super }}{% endif %}{% endblock %}
|
{% block title %}{% if page %}{{ block.super }}{% endif %}{% endblock %}
|
||||||
|
|
|
@ -1,6 +1,11 @@
|
||||||
{% extends "./dashboard/base.html" %}
|
{% extends "./dashboard/base.html" %}
|
||||||
{% load static aircox_admin i18n %}
|
{% load static aircox_admin i18n %}
|
||||||
|
|
||||||
|
{% block assets %}
|
||||||
|
{{ block.super }}
|
||||||
|
<script src="{% static "aircox/js/dashboard.js" %}"></script>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
{% block init-scripts %}
|
{% block init-scripts %}
|
||||||
aircox.labels = {% inline_labels %}
|
aircox.labels = {% inline_labels %}
|
||||||
{{ block.super }}
|
{{ block.super }}
|
||||||
|
@ -81,11 +86,9 @@ aircox.labels = {% inline_labels %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
<hr/>
|
<hr/>
|
||||||
<div class="flex-row">
|
<div class="has-text-right">
|
||||||
<div class="flex-grow-1">{% block page-form-actions %}{% endblock %}</div>
|
<button type="submit" class="button">{% translate "Update" %}</button>
|
||||||
<div class="has-text-right">
|
</div>
|
||||||
<button type="submit" class="button">{% translate "Update" %}</button>
|
|
||||||
</div>
|
|
||||||
</form>
|
</form>
|
||||||
</section>
|
</section>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
@ -6,13 +6,13 @@
|
||||||
{{ form.media }}
|
{{ form.media }}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block page-form-actions %}
|
{% block page-form %}
|
||||||
{% if request.user.is_superuser %}
|
//////
|
||||||
<button type="button"
|
|
||||||
class="button secondary"
|
|
||||||
@click="$refs['group-users-modal'].open({id: {{ object.editors_group_id }}, name: '{{ object.editors_group.name }}' })">Editors</button>
|
|
||||||
|
|
||||||
{% include "./dashboard/widgets/group_users.html" %}
|
|
||||||
{{ block.super }}
|
{{ block.super }}
|
||||||
|
|
||||||
|
{% if editors_formset %}
|
||||||
|
<hr/>
|
||||||
|
<h2 class="title is-2">{% translate "Editors" %}</h2>
|
||||||
|
{% include "./widgets/usergroup_formset.html" with formset=editors_formset formset_data=editors_formset_data tag_id="usergroup_formset" %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
{% extends "./base.html" %}
|
{% extends base_template|default:"./base.html" %}
|
||||||
|
|
||||||
|
|
||||||
{% comment %}
|
{% comment %}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
<a-autocomplete
|
<a-autocomplete
|
||||||
url="{{url}}"
|
url="{{url}}"
|
||||||
{% if ":name" not in widget.attrs %}name="{{ name|default:widget.name }}"{% endif %}{% if widget.value != None %} model-value="{{ widget.value|stringformat:'s' }}"{% endif %}
|
name="{{ widget.name }}"{% if widget.value != None %} model-value="{{ widget.value|stringformat:'s' }}"{% endif %}
|
||||||
{% include "django/forms/widgets/attrs.html" %} {{ extra|default:"" }}/>
|
{% include "django/forms/widgets/attrs.html" %} />
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
|
|
||||||
{% block row-control %}
|
{% block row-control %}
|
||||||
{% if name == 'user' %}
|
{% if name == 'user' %}
|
||||||
{% form_field field "inputName" value %}
|
{% form_field field name value %}
|
||||||
{% else %}
|
{% else %}
|
||||||
{{ block.super }}
|
{{ block.super }}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
|
@ -45,7 +45,7 @@ def do_formset_inline_data(context, formset):
|
||||||
# hack for sound list
|
# hack for sound list
|
||||||
if duration := item.get("duration"):
|
if duration := item.get("duration"):
|
||||||
item["duration"] = duration.strftime("%H:%M")
|
item["duration"] = duration.strftime("%H:%M")
|
||||||
if sound := getattr(form.instance, "sound", None):
|
if sound := getattr(form.instance, "sound"):
|
||||||
item["name"] = sound.name
|
item["name"] = sound.name
|
||||||
fields["name"] = str(_("Sound")).capitalize()
|
fields["name"] = str(_("Sound")).capitalize()
|
||||||
|
|
||||||
|
@ -55,7 +55,7 @@ def do_formset_inline_data(context, formset):
|
||||||
item["tags"] = ", ".join(tag.name for tag in tags)
|
item["tags"] = ", ".join(tag.name for tag in tags)
|
||||||
items.append(item)
|
items.append(item)
|
||||||
|
|
||||||
data = {"items": items, "fields": fields, "initial": formset.initial and formset.initial[0]}
|
data = {"items": items, "fields": fields}
|
||||||
user = context["request"].user
|
user = context["request"].user
|
||||||
settings = getattr(user, "aircox_settings", None)
|
settings = getattr(user, "aircox_settings", None)
|
||||||
data["settings"] = settings and UserSettingsSerializer(settings).data
|
data["settings"] = settings and UserSettingsSerializer(settings).data
|
||||||
|
|
|
@ -21,13 +21,11 @@ register_converter(WeekConverter, "week")
|
||||||
|
|
||||||
|
|
||||||
router = DefaultRouter()
|
router = DefaultRouter()
|
||||||
router.register("user", viewsets.UserViewSet, basename="user")
|
|
||||||
router.register("usergroup", viewsets.UserGroupViewSet, basename="usergroup")
|
|
||||||
|
|
||||||
router.register("images", viewsets.ImageViewSet, basename="image")
|
router.register("images", viewsets.ImageViewSet, basename="image")
|
||||||
router.register("sound", viewsets.SoundViewSet, basename="sound")
|
router.register("sound", viewsets.SoundViewSet, basename="sound")
|
||||||
router.register("track", viewsets.TrackROViewSet, basename="track")
|
router.register("track", viewsets.TrackROViewSet, basename="track")
|
||||||
router.register("comment", viewsets.CommentViewSet, basename="comment")
|
router.register("comment", viewsets.CommentViewSet, basename="comment")
|
||||||
|
router.register("usergroup", viewsets.UserGroupViewSet, basename="usergroup")
|
||||||
|
|
||||||
|
|
||||||
api = [
|
api = [
|
||||||
|
|
|
@ -115,9 +115,6 @@ class VueFormDataMixin:
|
||||||
|
|
||||||
# Note: values corresponds to AFormSet expected one
|
# Note: values corresponds to AFormSet expected one
|
||||||
|
|
||||||
def get_form_items(self, formset):
|
|
||||||
return [form.initial for form in formset.forms]
|
|
||||||
|
|
||||||
def get_form_field_data(self, form, values=None):
|
def get_form_field_data(self, form, values=None):
|
||||||
"""Return form fields as data."""
|
"""Return form fields as data."""
|
||||||
model = form.Meta.model
|
model = form.Meta.model
|
||||||
|
@ -143,7 +140,5 @@ class VueFormDataMixin:
|
||||||
"max_num_forms": formset.max_num,
|
"max_num_forms": formset.max_num,
|
||||||
},
|
},
|
||||||
"fields": self.get_form_field_data(formset.form, field_values),
|
"fields": self.get_form_field_data(formset.form, field_values),
|
||||||
"initial_extra": formset.initial_extra and formset.initial_extra[0],
|
|
||||||
"initials": self.get_form_items(formset),
|
|
||||||
**kwargs,
|
**kwargs,
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import random
|
import random
|
||||||
|
|
||||||
|
from django.contrib.auth.models import User
|
||||||
from django.contrib.auth.mixins import UserPassesTestMixin
|
from django.contrib.auth.mixins import UserPassesTestMixin
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
|
|
||||||
|
@ -60,3 +61,31 @@ class ProgramUpdateView(UserPassesTestMixin, VueFormDataMixin, PageUpdateView):
|
||||||
def test_func(self):
|
def test_func(self):
|
||||||
obj = self.get_object()
|
obj = self.get_object()
|
||||||
return permissions.program.can(self.request.user, "update", obj)
|
return permissions.program.can(self.request.user, "update", obj)
|
||||||
|
|
||||||
|
def get_editors_queryset(self, program):
|
||||||
|
# TODO: provide username in formset initials
|
||||||
|
return User.groups.through.objects.filter(group_id=program.editors_group_id).order_by("user__username")
|
||||||
|
|
||||||
|
def get_editors_formset(self, program, **kwargs):
|
||||||
|
return forms.UserGroupFormSet(
|
||||||
|
**{
|
||||||
|
**kwargs,
|
||||||
|
"prefix": "editors",
|
||||||
|
"queryset": self.get_editors_queryset(program),
|
||||||
|
"initial": {
|
||||||
|
"group": program.editors_group_id,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_context_data(self, editors_formset=None, **kwargs):
|
||||||
|
# TODO: use group and permission system
|
||||||
|
if self.request.user.is_superuser:
|
||||||
|
if editors_formset is None:
|
||||||
|
editors_formset = self.get_editors_formset(self.object)
|
||||||
|
kwargs["editors_formset_data"] = self.get_formset_data(
|
||||||
|
editors_formset, {"group": self.object.editors_group_id}
|
||||||
|
)
|
||||||
|
|
||||||
|
context = super().get_context_data(editors_formset=editors_formset, **kwargs)
|
||||||
|
return context
|
||||||
|
|
|
@ -40,49 +40,6 @@ class AutocompleteMixin:
|
||||||
return self.list(request)
|
return self.list(request)
|
||||||
|
|
||||||
|
|
||||||
class ListCommitMixin:
|
|
||||||
@action(name="commit", detail=False, methods=["POST"])
|
|
||||||
def commit(self, request):
|
|
||||||
"""
|
|
||||||
Data:
|
|
||||||
{
|
|
||||||
"delete": [pk],
|
|
||||||
"update": [{pk, **object}],
|
|
||||||
"create": [object_data]
|
|
||||||
}
|
|
||||||
|
|
||||||
Return:
|
|
||||||
{
|
|
||||||
"deleted": [pk],
|
|
||||||
"updated": [object],
|
|
||||||
"created": [object],
|
|
||||||
}
|
|
||||||
"""
|
|
||||||
queryset = self.get_queryset()
|
|
||||||
resp = {"deleted": [], "updated": [], "created": []}
|
|
||||||
if ids := request.data.get("delete"):
|
|
||||||
q = queryset.filter(id__in=ids)
|
|
||||||
resp["deleted"] = list(q.values_list("id", flat=True))
|
|
||||||
q.delete()
|
|
||||||
|
|
||||||
# TODO: bulk save and update
|
|
||||||
if items := request.data.get("update"):
|
|
||||||
resp["updated"] = self._commit_save_many(items)
|
|
||||||
|
|
||||||
if items := request.data.get("create"):
|
|
||||||
resp["created"] = self._commit_save_many(items)
|
|
||||||
|
|
||||||
return Response(data=resp)
|
|
||||||
|
|
||||||
def _commit_save_many(self, data):
|
|
||||||
ser = self.get_serializer(data=data, many=True)
|
|
||||||
ser.is_valid(raise_exception=True)
|
|
||||||
|
|
||||||
items = ser.save()
|
|
||||||
ser = self.get_serializer(items, many=True)
|
|
||||||
return ser.data
|
|
||||||
|
|
||||||
|
|
||||||
class ImageViewSet(viewsets.ModelViewSet):
|
class ImageViewSet(viewsets.ModelViewSet):
|
||||||
parsers = (parsers.MultiPartParser,)
|
parsers = (parsers.MultiPartParser,)
|
||||||
permissions = (permissions.IsAuthenticatedOrReadOnly,)
|
permissions = (permissions.IsAuthenticatedOrReadOnly,)
|
||||||
|
@ -127,6 +84,7 @@ class TrackROViewSet(AutocompleteMixin, viewsets.ReadOnlyModelViewSet):
|
||||||
|
|
||||||
serializer_class = serializers.admin.TrackSerializer
|
serializer_class = serializers.admin.TrackSerializer
|
||||||
permission_classes = (permissions.IsAuthenticated,)
|
permission_classes = (permissions.IsAuthenticated,)
|
||||||
|
filter_backends = (drf_filters.DjangoFilterBackend,)
|
||||||
filterset_class = filters.TrackFilterSet
|
filterset_class = filters.TrackFilterSet
|
||||||
queryset = models.Track.objects.all()
|
queryset = models.Track.objects.all()
|
||||||
|
|
||||||
|
@ -138,19 +96,10 @@ class CommentViewSet(viewsets.ModelViewSet):
|
||||||
|
|
||||||
|
|
||||||
# --- admin
|
# --- admin
|
||||||
class UserViewSet(AutocompleteMixin, viewsets.ModelViewSet):
|
class UserGroupViewSet(AutocompleteMixin, viewsets.ModelViewSet):
|
||||||
serializer_class = serializers.auth.UserSerializer
|
|
||||||
permission_classes = (permissions.IsAdminUser,)
|
|
||||||
filterset_class = filters.UserFilterSet
|
|
||||||
queryset = User.objects.all().distinct().order_by("username")
|
|
||||||
|
|
||||||
|
|
||||||
class UserGroupViewSet(ListCommitMixin, viewsets.ModelViewSet):
|
|
||||||
serializer_class = serializers.auth.UserGroupSerializer
|
serializer_class = serializers.auth.UserGroupSerializer
|
||||||
permission_classes = (permissions.IsAdminUser,)
|
permission_classes = (permissions.IsAdminUser,)
|
||||||
filterset_class = filters.UserGroupFilterSet
|
queryset = User.groups.through.objects.all().distinct().order_by("user__username")
|
||||||
model = User.groups.through
|
|
||||||
queryset = model.objects.all().distinct().order_by("user__username")
|
|
||||||
|
|
||||||
|
|
||||||
class UserSettingsViewSet(viewsets.ViewSet):
|
class UserSettingsViewSet(viewsets.ViewSet):
|
||||||
|
|
|
@ -1,29 +1,24 @@
|
||||||
# aircox
|
# aircox-assets
|
||||||
|
|
||||||
This template should help get you started developing with Vue 3 in Vite.
|
## Project setup
|
||||||
|
```
|
||||||
## Recommended IDE Setup
|
|
||||||
|
|
||||||
[VSCode](https://code.visualstudio.com/) + [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) (and disable Vetur).
|
|
||||||
|
|
||||||
## Customize configuration
|
|
||||||
|
|
||||||
See [Vite Configuration Reference](https://vitejs.dev/config/).
|
|
||||||
|
|
||||||
## Project Setup
|
|
||||||
|
|
||||||
```sh
|
|
||||||
npm install
|
npm install
|
||||||
```
|
```
|
||||||
|
|
||||||
### Compile and Hot-Reload for Development
|
### Compiles and hot-reloads for development
|
||||||
|
```
|
||||||
```sh
|
npm run serve
|
||||||
npm run dev
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### Compile and Minify for Production
|
### Compiles and minifies for production
|
||||||
|
```
|
||||||
```sh
|
|
||||||
npm run build
|
npm run build
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Lints and fixes files
|
||||||
|
```
|
||||||
|
npm run lint
|
||||||
|
```
|
||||||
|
|
||||||
|
### Customize configuration
|
||||||
|
See [Configuration Reference](https://cli.vuejs.org/config/).
|
||||||
|
|
5
assets/babel.config.js
Normal file
5
assets/babel.config.js
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
module.exports = {
|
||||||
|
presets: [
|
||||||
|
'@vue/cli-plugin-babel/preset'
|
||||||
|
]
|
||||||
|
}
|
|
@ -1,8 +1,19 @@
|
||||||
{
|
{
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
|
"target": "es5",
|
||||||
|
"module": "esnext",
|
||||||
|
"baseUrl": "./",
|
||||||
|
"moduleResolution": "node",
|
||||||
"paths": {
|
"paths": {
|
||||||
"@/*": ["./src/*"]
|
"@/*": [
|
||||||
}
|
"src/*"
|
||||||
},
|
]
|
||||||
"exclude": ["node_modules", "dist"]
|
},
|
||||||
|
"lib": [
|
||||||
|
"esnext",
|
||||||
|
"dom",
|
||||||
|
"dom.iterable",
|
||||||
|
"scripthost"
|
||||||
|
]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,37 +1,39 @@
|
||||||
{
|
{
|
||||||
"name": "aircox",
|
"name": "aircox-assets",
|
||||||
"version": "0.0.0",
|
"version": "0.1.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"sideEffects": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"serve": "vue-cli-service serve",
|
||||||
"build": "vite build",
|
"build": "vue-cli-service build",
|
||||||
"watch": "vite build --watch",
|
"lint": "vue-cli-service lint"
|
||||||
"preview": "vite preview"
|
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@fortawesome/fontawesome-free": "^6.0.0",
|
"@fortawesome/fontawesome-free": "^6.0.0",
|
||||||
"@popperjs/core": "^2.11.8",
|
"@popperjs/core": "^2.11.8",
|
||||||
"@rollup/plugin-commonjs": "^25.0.7",
|
|
||||||
"core-js": "^3.8.3",
|
"core-js": "^3.8.3",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"v-calendar": "^3.1.2",
|
"v-calendar": "^3.1.2",
|
||||||
"vite-plugin-babel-macros": "^1.0.6",
|
"vue": "^3.2.13"
|
||||||
"vue": "^3.4.21"
|
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@vitejs/plugin-vue": "^5.0.4",
|
"@babel/core": "^7.12.16",
|
||||||
|
"@babel/eslint-parser": "^7.12.16",
|
||||||
|
"@vue/cli-plugin-babel": "~5.0.0",
|
||||||
|
"@vue/cli-plugin-eslint": "~5.0.0",
|
||||||
|
"@vue/cli-service": "~5.0.0",
|
||||||
"bulma": "^0.9.4",
|
"bulma": "^0.9.4",
|
||||||
"eslint": "^7.32.0",
|
"eslint": "^7.32.0",
|
||||||
"eslint-plugin-vue": "^8.0.3",
|
"eslint-plugin-vue": "^8.0.3",
|
||||||
"sass": "^1.49.9",
|
"sass": "^1.49.9",
|
||||||
"vite": "^5.2.8"
|
"sass-loader": "^12.6.0",
|
||||||
|
"vue-cli": "^2.9.6",
|
||||||
|
"webpack-cli": "^5.1.4"
|
||||||
},
|
},
|
||||||
"eslintConfig": {
|
"eslintConfig": {
|
||||||
"root": true,
|
"root": true,
|
||||||
"env": {
|
"env": {
|
||||||
"node": true,
|
"node": true
|
||||||
"es2022": true
|
|
||||||
},
|
},
|
||||||
"extends": [
|
"extends": [
|
||||||
"plugin:vue/vue3-essential",
|
"plugin:vue/vue3-essential",
|
||||||
|
|
BIN
assets/public/logo.png
Normal file
BIN
assets/public/logo.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 7.1 KiB |
1
assets/public/vue.esm-browser.js
Symbolic link
1
assets/public/vue.esm-browser.js
Symbolic link
|
@ -0,0 +1 @@
|
||||||
|
../node_modules/vue/dist/vue.esm-browser.js
|
1
assets/public/vue.esm-browser.prod.js
Symbolic link
1
assets/public/vue.esm-browser.prod.js
Symbolic link
|
@ -0,0 +1 @@
|
||||||
|
../node_modules/vue/dist/vue.esm-browser.prod.js
|
|
@ -2,7 +2,7 @@ import './styles/admin.scss'
|
||||||
import './index.js'
|
import './index.js'
|
||||||
|
|
||||||
import App from './app';
|
import App from './app';
|
||||||
import components from './components/admin.js'
|
import {admin as components} from './components'
|
||||||
|
|
||||||
const AdminApp = {
|
const AdminApp = {
|
||||||
...App,
|
...App,
|
||||||
|
@ -11,21 +11,7 @@ const AdminApp = {
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
...super.data,
|
...super.data,
|
||||||
modalItem: null,
|
|
||||||
}
|
}
|
||||||
},
|
|
||||||
|
|
||||||
methods: {
|
|
||||||
...App.methods,
|
|
||||||
|
|
||||||
fileSelected(select, input, preview) {
|
|
||||||
const item = this.$refs[select].item
|
|
||||||
if(item) {
|
|
||||||
this.$refs[input].value = item.id
|
|
||||||
if(preview)
|
|
||||||
preview.src = item.file
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
export default AdminApp;
|
export default AdminApp;
|
||||||
|
|
|
@ -17,22 +17,10 @@ const App = {
|
||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
//! Delete elements from DOM using provided selector.
|
|
||||||
deleteElements(sel) {
|
deleteElements(sel) {
|
||||||
for(var el of document.querySelectorAll(sel))
|
for(var el of document.querySelectorAll(sel))
|
||||||
el.parentNode.removeChild(el)
|
el.parentNode.removeChild(el)
|
||||||
},
|
}
|
||||||
|
|
||||||
//! File has been selected
|
|
||||||
//! TODO: replace using regular ref and bindings.
|
|
||||||
fileSelected(select, input, preview) {
|
|
||||||
const item = this.$refs[select].item
|
|
||||||
if(item) {
|
|
||||||
this.$refs[input].value = item.id
|
|
||||||
if(preview)
|
|
||||||
preview.src = item.file
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -20,23 +20,24 @@
|
||||||
<span class="is-inline-block" v-if="selected">
|
<span class="is-inline-block" v-if="selected">
|
||||||
<slot name="button" :index="selectedIndex" :item="selected"
|
<slot name="button" :index="selectedIndex" :item="selected"
|
||||||
:value-field="valueField" :labelField="labelField">
|
:value-field="valueField" :labelField="labelField">
|
||||||
{{ selectedLabel }}
|
{{ labelField && selected.data[labelField] || selected }}
|
||||||
</slot>
|
</slot>
|
||||||
</span>
|
</span>
|
||||||
</a>
|
</a>
|
||||||
<div :class="dropdownClass">
|
<div :class="dropdownClass">
|
||||||
<div class="dropdown-menu is-fullwidth">
|
<div class="dropdown-menu is-fullwidth">
|
||||||
<div class="dropdown-content" style="overflow: hidden">
|
<div class="dropdown-content" style="overflow: hidden">
|
||||||
<span v-for="(item, index) in items" :key="item.id"
|
<a v-for="(item, index) in items" :key="item.id"
|
||||||
:data-autocomplete-index="index"
|
href="#" :data-autocomplete-index="index"
|
||||||
@click="select(index, false, false)"
|
@click="select(index, false, false)"
|
||||||
:class="['dropdown-item', (index == this.cursor) ? 'is-active':'']"
|
:class="['dropdown-item', (index == this.cursor) ? 'is-active':'']"
|
||||||
|
:title="labelField && item.data[labelField] || item"
|
||||||
tabindex="-1">
|
tabindex="-1">
|
||||||
<slot name="item" :index="index" :item="item" :value-field="valueField"
|
<slot name="item" :index="index" :item="item" :value-field="valueField"
|
||||||
:labelField="labelField">
|
:labelField="labelField">
|
||||||
{{ getValue(item, labelField) || item }}
|
{{ labelField && item.data[labelField] || item }}
|
||||||
</slot>
|
</slot>
|
||||||
</span>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -55,14 +56,12 @@ export default {
|
||||||
props: {
|
props: {
|
||||||
//! Search URL (where `${query}` is replaced by search term)
|
//! Search URL (where `${query}` is replaced by search term)
|
||||||
url: String,
|
url: String,
|
||||||
//! Extra GET url parameters
|
|
||||||
urlParams: Object,
|
|
||||||
//! Items' model
|
//! Items' model
|
||||||
model: Function,
|
model: Function,
|
||||||
//! Input tag class
|
//! Input tag class
|
||||||
inputClass: Array,
|
inputClass: Array,
|
||||||
//! input text placeholder
|
//! input text placeholder
|
||||||
placeholder: Object,
|
placeholder: String,
|
||||||
//! input form field name
|
//! input form field name
|
||||||
name: String,
|
name: String,
|
||||||
//! Field on items to use as label
|
//! Field on items to use as label
|
||||||
|
@ -106,20 +105,6 @@ export default {
|
||||||
},
|
},
|
||||||
|
|
||||||
computed: {
|
computed: {
|
||||||
fullUrl() {
|
|
||||||
if(!this.urlParams)
|
|
||||||
return this.url
|
|
||||||
|
|
||||||
const url = new URL(this.url, window.location.origin)
|
|
||||||
const params = new URLSearchParams(url.searchParams)
|
|
||||||
|
|
||||||
for(var key in this.urlParams)
|
|
||||||
params.set(key, this.urlParams[key])
|
|
||||||
const join = this.url.indexOf("?") >= 0 ? "&" : "?"
|
|
||||||
url.search = params.toString()
|
|
||||||
return url.href
|
|
||||||
},
|
|
||||||
|
|
||||||
isFetching() { return !!this.promise },
|
isFetching() { return !!this.promise },
|
||||||
|
|
||||||
selected() {
|
selected() {
|
||||||
|
@ -151,34 +136,12 @@ export default {
|
||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
reset() {
|
|
||||||
this.inputValue = ""
|
|
||||||
this.selectedIndex = -1
|
|
||||||
this.items = []
|
|
||||||
},
|
|
||||||
|
|
||||||
// TODO: move to utils/data
|
|
||||||
getValue(data, path=null) {
|
|
||||||
if(!data)
|
|
||||||
return null
|
|
||||||
if(!path)
|
|
||||||
return data
|
|
||||||
|
|
||||||
const paths = path.split('.')
|
|
||||||
for(const key of paths) {
|
|
||||||
if(key in data)
|
|
||||||
data = data[key]
|
|
||||||
else return null;
|
|
||||||
}
|
|
||||||
return data
|
|
||||||
},
|
|
||||||
|
|
||||||
itemValue(item) {
|
itemValue(item) {
|
||||||
return this.valueField ? this.getValue(item, this.valueField) : item;
|
return this.valueField ? item && item[this.valueField] : item;
|
||||||
},
|
},
|
||||||
|
|
||||||
itemLabel(item) {
|
itemLabel(item) {
|
||||||
return this.labelField ? this.getValue(item, this.labelField) : item;
|
return this.labelField ? item && item[this.labelField] : item;
|
||||||
},
|
},
|
||||||
|
|
||||||
hide() {
|
hide() {
|
||||||
|
@ -220,7 +183,7 @@ export default {
|
||||||
if(!this.items.length)
|
if(!this.items.length)
|
||||||
return
|
return
|
||||||
|
|
||||||
var index = event.relatedTarget && Math.floor(event.relatedTarget.dataset.autocompleteIndex);
|
var index = event.relatedTarget && Math.parseInt(event.relatedTarget.dataset.autocompleteIndex);
|
||||||
if(index !== undefined && index !== null)
|
if(index !== undefined && index !== null)
|
||||||
this.select(index, false, false)
|
this.select(index, false, false)
|
||||||
this.cursor = -1;
|
this.cursor = -1;
|
||||||
|
@ -264,7 +227,7 @@ export default {
|
||||||
return
|
return
|
||||||
|
|
||||||
this.query = query
|
this.query = query
|
||||||
var url = this.fullUrl.replace('${query}', query).replace('%24%7Bquery%7D', query)
|
var url = this.url.replace('${query}', query)
|
||||||
var promise = this.model ? this.model.fetch(url, {many:true})
|
var promise = this.model ? this.model.fetch(url, {many:true})
|
||||||
: fetch(url, Model.getOptions()).then(d => d.json())
|
: fetch(url, Model.getOptions()).then(d => d.json())
|
||||||
|
|
||||||
|
|
|
@ -3,9 +3,9 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import {Set} from '../model.js';
|
import {Set} from '../model';
|
||||||
import Sound from '../sound.js';
|
import Sound from '../sound';
|
||||||
import APage from './APage.vue';
|
import APage from './APage';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
extends: APage,
|
extends: APage,
|
||||||
|
|
|
@ -26,7 +26,7 @@
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script>
|
<script>
|
||||||
import {getCsrf} from "../model.js"
|
import {getCsrf} from "../model"
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
emit: ["fileChange", "load", "abort", "error"],
|
emit: ["fileChange", "load", "abort", "error"],
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
:value="value"/>
|
:value="value"/>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<a-rows ref="rows" :set="set" :context="this"
|
<a-rows ref="rows" :set="set"
|
||||||
:columns="visibleFields" :columnsOrderable="columnsOrderable"
|
:columns="visibleFields" :columnsOrderable="columnsOrderable"
|
||||||
:orderable="orderable" @move="moveItem" @colmove="onColumnMove"
|
:orderable="orderable" @move="moveItem" @colmove="onColumnMove"
|
||||||
@cell="e => $emit('cell', e)">
|
@cell="e => $emit('cell', e)">
|
||||||
|
@ -45,7 +45,7 @@
|
||||||
|
|
||||||
<template v-for="(field,slot) of fieldSlots" v-bind:key="field.name"
|
<template v-for="(field,slot) of fieldSlots" v-bind:key="field.name"
|
||||||
v-slot:[slot]="data">
|
v-slot:[slot]="data">
|
||||||
<slot :name="slot" v-bind="data" :field="field" :input-name="_prefix + data.cell.row + '-' + field.name">
|
<slot :name="slot" v-bind="data" :field="field" :input-name="_prefix + data.cell.row + '-' + field.name">
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<div class="control">
|
<div class="control">
|
||||||
<slot :name="'control-' + field.name" v-bind="data" :field="field" :input-name="_prefix + data.cell.row + '-' + field.name"/>
|
<slot :name="'control-' + field.name" v-bind="data" :field="field" :input-name="_prefix + data.cell.row + '-' + field.name"/>
|
||||||
|
@ -118,6 +118,8 @@
|
||||||
formData: Object,
|
formData: Object,
|
||||||
//! Model class used for item's set
|
//! Model class used for item's set
|
||||||
model: {type: Function, default: Model},
|
model: {type: Function, default: Model},
|
||||||
|
//! initial data set load at mount
|
||||||
|
initials: Array,
|
||||||
},
|
},
|
||||||
|
|
||||||
data() {
|
data() {
|
||||||
|
@ -182,7 +184,7 @@
|
||||||
|
|
||||||
//! Reset forms to initials
|
//! Reset forms to initials
|
||||||
reset() {
|
reset() {
|
||||||
this.load(this.formData?.initials || [], true)
|
this.load(this.initials || [], true)
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
@ -1,102 +0,0 @@
|
||||||
<template>
|
|
||||||
<div class="a-group-users">
|
|
||||||
<table class="table is-fullwidth">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>
|
|
||||||
Members
|
|
||||||
</th>
|
|
||||||
<th style="width: 1rem">
|
|
||||||
<span class="icon">
|
|
||||||
<i class="fa fa-trash"/>
|
|
||||||
</span>
|
|
||||||
</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
<template v-for="item of items" :key="item.id">
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
<b class="mr-3">{{ item.data.user.username }}</b>
|
|
||||||
<span class="text-light">{{ item.data.user.first_name }} {{ item.data.user.last_name }}</span>
|
|
||||||
</td>
|
|
||||||
<td class="align-center">
|
|
||||||
<input type="checkbox" class="checkbox" @change="item.deleted = $event.target.checked">
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</template>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
<div>
|
|
||||||
<label>
|
|
||||||
<span class="icon">
|
|
||||||
<i class="fa fa-user"/>
|
|
||||||
</span>
|
|
||||||
Add user
|
|
||||||
</label>
|
|
||||||
<a-autocomplete ref="autocomplete" :url="searchUrl"
|
|
||||||
label-field="username" value-field="id"
|
|
||||||
@select="onUserSelect">
|
|
||||||
<template #item="{item}">
|
|
||||||
<b class="mr-3">{{ item.username }}</b>
|
|
||||||
<span class="text-light">{{ item.first_name }} {{ item.last_name }}</span>
|
|
||||||
—
|
|
||||||
<i>{{ item.email }}</i>
|
|
||||||
</template>
|
|
||||||
</a-autocomplete>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
<script>
|
|
||||||
import Model, { Set } from "../model.js"
|
|
||||||
import AAutocomplete from "./AAutocomplete.vue"
|
|
||||||
|
|
||||||
export default {
|
|
||||||
components: {AAutocomplete},
|
|
||||||
props: {
|
|
||||||
model: {type: Function, default: Model },
|
|
||||||
// List url
|
|
||||||
url: String,
|
|
||||||
// User autocomplete url
|
|
||||||
searchUrl: String,
|
|
||||||
// POST url
|
|
||||||
commitUrl: String,
|
|
||||||
// default values
|
|
||||||
initials: {type: Object, default: () => ({})},
|
|
||||||
},
|
|
||||||
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
set: new Set(this.model, {url: this.url, unique: true}),
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
computed: {
|
|
||||||
items() { return this.set?.items || [] },
|
|
||||||
user_ids() { return this.set?.items.map(i => i.data.user.id) },
|
|
||||||
},
|
|
||||||
|
|
||||||
methods: {
|
|
||||||
onUserSelect(index, item, value) {
|
|
||||||
if(this.user_ids.indexOf(item.id) != -1)
|
|
||||||
return
|
|
||||||
|
|
||||||
this.set.push({
|
|
||||||
...this.initials,
|
|
||||||
user: {...item},
|
|
||||||
})
|
|
||||||
this.$refs.autocomplete.reset()
|
|
||||||
},
|
|
||||||
|
|
||||||
save() {
|
|
||||||
this.set.commit(this.commitUrl, {
|
|
||||||
getData: i => ({...this.initials, user_id: i.data.user.id})
|
|
||||||
})
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
mounted() {
|
|
||||||
this.set.fetch()
|
|
||||||
},
|
|
||||||
}
|
|
||||||
</script>
|
|
|
@ -4,9 +4,9 @@
|
||||||
<div class="modal-card">
|
<div class="modal-card">
|
||||||
<header class="modal-card-head">
|
<header class="modal-card-head">
|
||||||
<div class="modal-card-title">
|
<div class="modal-card-title">
|
||||||
<slot name="title" :item="item">{{ title }}</slot>
|
<slot name="title">{{ title }}</slot>
|
||||||
</div>
|
</div>
|
||||||
<slot name="bar" :item="item"></slot>
|
<slot name="bar"></slot>
|
||||||
<button type="button" class="delete square" aria-label="close" @click="close">
|
<button type="button" class="delete square" aria-label="close" @click="close">
|
||||||
<span class="icon">
|
<span class="icon">
|
||||||
<i class="fa fa-close"></i>
|
<i class="fa fa-close"></i>
|
||||||
|
|
|
@ -1,25 +1,25 @@
|
||||||
<template>
|
<template>
|
||||||
<tr>
|
<tr>
|
||||||
<slot name="head" :context="context" :item="item" :row="row"/>
|
<slot name="head" :item="item" :row="row"/>
|
||||||
<template v-for="(attr,col) in columns" :key="col">
|
<template v-for="(attr,col) in columns" :key="col">
|
||||||
<slot name="cell-before" :context="context" :item="item" :cell="cells[col]"
|
<slot name="cell-before" :item="item" :cell="cells[col]"
|
||||||
:attr="attr"/>
|
:attr="attr"/>
|
||||||
<component :is="cellTag" :class="['cell', 'cell-' + attr]" :data-col="col"
|
<component :is="cellTag" :class="['cell', 'cell-' + attr]" :data-col="col"
|
||||||
:draggable="orderable"
|
:draggable="orderable"
|
||||||
@dragstart="onDragStart" @dragover="onDragOver" @drop="onDrop">
|
@dragstart="onDragStart" @dragover="onDragOver" @drop="onDrop">
|
||||||
<slot :name="attr" :context="context" :item="item" :cell="cells[col]"
|
<slot :name="attr" :item="item" :cell="cells[col]"
|
||||||
:data="itemData" :attr="attr" :emit="cellEmit"
|
:data="itemData" :attr="attr" :emit="cellEmit"
|
||||||
:value="itemData && itemData[attr]">
|
:value="itemData && itemData[attr]">
|
||||||
{{ itemData && itemData[attr] }}
|
{{ itemData && itemData[attr] }}
|
||||||
</slot>
|
</slot>
|
||||||
<slot name="cell" :context="context" :item="item" :cell="cells[col]"
|
<slot name="cell" :item="item" :cell="cells[col]"
|
||||||
:data="itemData" :attr="attr" :emit="cellEmit"
|
:data="itemData" :attr="attr" :emit="cellEmit"
|
||||||
:value="itemData && itemData[attr]"/>
|
:value="itemData && itemData[attr]"/>
|
||||||
</component>
|
</component>
|
||||||
<slot name="cell-after" :context="context" :item="item" :col="col" :cell="cells[col]"
|
<slot name="cell-after" :item="item" :col="col" :cell="cells[col]"
|
||||||
:attr="attr"/>
|
:attr="attr"/>
|
||||||
</template>
|
</template>
|
||||||
<slot name="tail" :context="context" :item="item" :row="row"/>
|
<slot name="tail" :item="item" :row="row"/>
|
||||||
</tr>
|
</tr>
|
||||||
</template>
|
</template>
|
||||||
<script>
|
<script>
|
||||||
|
@ -30,8 +30,6 @@ export default {
|
||||||
emits: ['move', 'cell'],
|
emits: ['move', 'cell'],
|
||||||
|
|
||||||
props: {
|
props: {
|
||||||
//! Context object
|
|
||||||
context: {type: Object, default: () => ({})},
|
|
||||||
//! Item to display in row
|
//! Item to display in row
|
||||||
item: {type: Object, default: () => ({})},
|
item: {type: Object, default: () => ({})},
|
||||||
//! Columns to display, as items' attributes
|
//! Columns to display, as items' attributes
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
<template>
|
<template>
|
||||||
<table class="table is-stripped is-fullwidth">
|
<table class="table is-stripped is-fullwidth">
|
||||||
<thead>
|
<thead>
|
||||||
<a-row :context="context" :columns="columnNames"
|
<a-row :columns="columnNames"
|
||||||
:orderable="columnsOrderable" cellTag="th"
|
:orderable="columnsOrderable" cellTag="th"
|
||||||
@move="moveColumn">
|
@move="moveColumn">
|
||||||
<template v-if="$slots['header-head']" v-slot:head="data">
|
<template v-if="$slots['header-head']" v-slot:head="data">
|
||||||
|
@ -26,7 +26,7 @@
|
||||||
<slot name="head"/>
|
<slot name="head"/>
|
||||||
<template v-for="(item,row) in items" :key="row">
|
<template v-for="(item,row) in items" :key="row">
|
||||||
<!-- data-index comes from AList component drag & drop -->
|
<!-- data-index comes from AList component drag & drop -->
|
||||||
<a-row :context="context" :item="item" :cell="{row}" :columns="columnNames" :data-index="row"
|
<a-row :item="item" :cell="{row}" :columns="columnNames" :data-index="row"
|
||||||
:data-row="row"
|
:data-row="row"
|
||||||
:draggable="orderable"
|
:draggable="orderable"
|
||||||
@dragstart="onDragStart" @dragover="onDragOver" @drop="onDrop"
|
@dragstart="onDragStart" @dragover="onDragOver" @drop="onDrop"
|
||||||
|
@ -54,9 +54,6 @@ const Component = {
|
||||||
|
|
||||||
props: {
|
props: {
|
||||||
...AList.props,
|
...AList.props,
|
||||||
//! Context object
|
|
||||||
context: {type: Object, default: () => ({})},
|
|
||||||
|
|
||||||
//! Ordered list of columns, as objects with:
|
//! Ordered list of columns, as objects with:
|
||||||
//! - name: item attribute value
|
//! - name: item attribute value
|
||||||
//! - label: display label
|
//! - label: display label
|
||||||
|
|
|
@ -1,23 +0,0 @@
|
||||||
import AFileUpload from "./AFileUpload.vue"
|
|
||||||
import ASelectFile from "./ASelectFile.vue"
|
|
||||||
import AStatistics from './AStatistics.vue'
|
|
||||||
import AStreamer from './AStreamer.vue'
|
|
||||||
|
|
||||||
import AFormSet from './AFormSet.vue'
|
|
||||||
import ATrackListEditor from './ATrackListEditor.vue'
|
|
||||||
import ASoundListEditor from './ASoundListEditor.vue'
|
|
||||||
|
|
||||||
import AGroupUsers from "./AGroupUsers.vue"
|
|
||||||
|
|
||||||
import base from "./index.js"
|
|
||||||
|
|
||||||
|
|
||||||
export const admin = {
|
|
||||||
...base,
|
|
||||||
AGroupUsers,
|
|
||||||
AFileUpload, ASelectFile,
|
|
||||||
AFormSet, ATrackListEditor, ASoundListEditor,
|
|
||||||
AStatistics, AStreamer,
|
|
||||||
}
|
|
||||||
|
|
||||||
export default admin
|
|
|
@ -1,26 +1,45 @@
|
||||||
import AAutocomplete from './AAutocomplete.vue'
|
|
||||||
import AModal from "./AModal.vue"
|
|
||||||
import AActionButton from './AActionButton.vue'
|
import AActionButton from './AActionButton.vue'
|
||||||
import ADropdown from "./ADropdown.vue"
|
import AAutocomplete from './AAutocomplete'
|
||||||
import ACarousel from './ACarousel.vue'
|
import ACarousel from './ACarousel'
|
||||||
import AEpisode from './AEpisode.vue'
|
import ADropdown from "./ADropdown"
|
||||||
import AList from './AList.vue'
|
import AEpisode from './AEpisode'
|
||||||
import APage from './APage.vue'
|
import AList from './AList'
|
||||||
import APlayer from './APlayer.vue'
|
import APage from './APage'
|
||||||
import APlaylist from './APlaylist.vue'
|
import APlayer from './APlayer'
|
||||||
import AProgress from './AProgress.vue'
|
import APlaylist from './APlaylist'
|
||||||
import ASoundItem from './ASoundItem.vue'
|
import AProgress from './AProgress'
|
||||||
import ASwitch from './ASwitch.vue'
|
import ASoundItem from './ASoundItem'
|
||||||
|
import ASwitch from './ASwitch'
|
||||||
|
|
||||||
|
import AModal from "./AModal"
|
||||||
|
import AFileUpload from "./AFileUpload"
|
||||||
|
import ASelectFile from "./ASelectFile"
|
||||||
|
import AStatistics from './AStatistics'
|
||||||
|
import AStreamer from './AStreamer'
|
||||||
|
|
||||||
|
import AFormSet from './AFormSet'
|
||||||
|
import ATrackListEditor from './ATrackListEditor'
|
||||||
|
import ASoundListEditor from './ASoundListEditor'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Core components
|
* Core components
|
||||||
*/
|
*/
|
||||||
export const base = {
|
export const base = {
|
||||||
AActionButton, AAutocomplete, AModal,
|
AAutocomplete, ACarousel, ADropdown, AEpisode, AList, APage, APlayer, APlaylist,
|
||||||
ACarousel, ADropdown, AEpisode, AList, APage, APlayer, APlaylist,
|
|
||||||
AProgress, ASoundItem, ASwitch,
|
AProgress, ASoundItem, ASwitch,
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default base
|
export default base
|
||||||
|
|
||||||
|
export const admin = {
|
||||||
|
...base,
|
||||||
|
ATrackListEditor
|
||||||
|
}
|
||||||
|
|
||||||
|
export const dashboard = {
|
||||||
|
...base,
|
||||||
|
AActionButton, AFileUpload, ASelectFile, AModal,
|
||||||
|
AFormSet, ATrackListEditor, ASoundListEditor,
|
||||||
|
AStatistics, AStreamer,
|
||||||
|
}
|
||||||
|
|
33
assets/src/dashboard.js
Normal file
33
assets/src/dashboard.js
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
import './styles/admin.scss'
|
||||||
|
import './index.js'
|
||||||
|
|
||||||
|
import App from './app';
|
||||||
|
import {dashboard as components} from './components'
|
||||||
|
|
||||||
|
const DashboardApp = {
|
||||||
|
...App,
|
||||||
|
components: {...App.components, ...components},
|
||||||
|
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
modalItem: null,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
...App.methods,
|
||||||
|
|
||||||
|
fileSelected(select, input, preview) {
|
||||||
|
const item = this.$refs[select].item
|
||||||
|
if(item) {
|
||||||
|
this.$refs[input].value = item.id
|
||||||
|
if(preview)
|
||||||
|
preview.src = item.file
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export default DashboardApp;
|
||||||
|
|
||||||
|
|
||||||
|
window.App = DashboardApp
|
|
@ -3,8 +3,6 @@
|
||||||
* administration interface)
|
* administration interface)
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import 'vue'
|
|
||||||
|
|
||||||
//-- aircox
|
//-- aircox
|
||||||
import App, {PlayerApp} from './app'
|
import App, {PlayerApp} from './app'
|
||||||
import VueLoader from './vueLoader'
|
import VueLoader from './vueLoader'
|
||||||
|
|
|
@ -113,7 +113,7 @@ export default class Model {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set instance's data with provided data. Return None
|
* Update instance's data with provided data. Return None
|
||||||
*/
|
*/
|
||||||
commit(data) {
|
commit(data) {
|
||||||
this.data = data;
|
this.data = data;
|
||||||
|
@ -121,17 +121,11 @@ export default class Model {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update model data, without reset previous value.
|
* Update model data, without reset previous value
|
||||||
* Item is marked as updated.
|
|
||||||
*/
|
*/
|
||||||
update(data) {
|
update(data) {
|
||||||
this.data = {...this.data, ...data}
|
this.data = {...this.data, ...data}
|
||||||
this.id = this.constructor.getId(this.data)
|
this.id = this.constructor.getId(this.data)
|
||||||
this.updated = true
|
|
||||||
}
|
|
||||||
|
|
||||||
delete() {
|
|
||||||
this.deleted = true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -183,24 +177,8 @@ export class Set {
|
||||||
this.push(item, {args: args, save: false});
|
this.push(item, {args: args, save: false});
|
||||||
}
|
}
|
||||||
|
|
||||||
//! Return total items count
|
|
||||||
get length() { return this.items.length }
|
get length() { return this.items.length }
|
||||||
|
|
||||||
//! Return a list of items marked as deleted
|
|
||||||
get deletedItems() {
|
|
||||||
return this.items.filter(i => i.deleted)
|
|
||||||
}
|
|
||||||
|
|
||||||
//! Return a list of created items
|
|
||||||
get createdItems() {
|
|
||||||
return this.items.filter(i => !i.deleted && !i.id)
|
|
||||||
}
|
|
||||||
|
|
||||||
//! Return a list of updated items
|
|
||||||
get updatedItems() {
|
|
||||||
return this.items.filter(i => i.updated)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fetch multiple items from server
|
* Fetch multiple items from server
|
||||||
*/
|
*/
|
||||||
|
@ -212,58 +190,6 @@ export class Set {
|
||||||
.map(d => new model(d, {url: url, ...args})))
|
.map(d => new model(d, {url: url, ...args})))
|
||||||
}
|
}
|
||||||
|
|
||||||
fetch({url=null, reset=false, ...options}={}, args=null) {
|
|
||||||
url = url || this.url
|
|
||||||
options = this.model.getOptions(options)
|
|
||||||
return fetch(url, options)
|
|
||||||
.then(response => response.json())
|
|
||||||
.then(data =>
|
|
||||||
(data instanceof Array ? data : data.results)
|
|
||||||
.map(d => new this.model(d, {url: url, ...args}))
|
|
||||||
)
|
|
||||||
.then(data => {
|
|
||||||
if(reset)
|
|
||||||
this.items = data
|
|
||||||
else
|
|
||||||
// TODO: remove duplicate
|
|
||||||
this.items = [...this.items, ...data]
|
|
||||||
return data
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Commit changes to server.
|
|
||||||
* ref: `views.mixin.ListCommitMixin`
|
|
||||||
*/
|
|
||||||
commit(url, {getData=null, ...options}={}) {
|
|
||||||
const createdItems = this.createdItems
|
|
||||||
const body = {
|
|
||||||
delete: this.deletedItems.map(i => i.id),
|
|
||||||
update: this.updatedItems.map(getData),
|
|
||||||
create: createdItems.map(getData),
|
|
||||||
}
|
|
||||||
if(!body.delete && !body.update && !body.create)
|
|
||||||
return
|
|
||||||
|
|
||||||
getData = getData || ((i) => i.data);
|
|
||||||
options = this.model.getOptions(options)
|
|
||||||
options.method = "POST"
|
|
||||||
options.body = JSON.stringify(body)
|
|
||||||
return fetch(url, options)
|
|
||||||
.then(response => response.json())
|
|
||||||
.then(data => {
|
|
||||||
const {created, updated, deleted} = data
|
|
||||||
if(createdItems)
|
|
||||||
this.items = this.items.filter(i => createdItems.indexOf(i) == -1)
|
|
||||||
if(deleted)
|
|
||||||
this.items = this.items.filter(i => deleted.indexOf(i.id) == -1)
|
|
||||||
|
|
||||||
this.extend(created)
|
|
||||||
this.extend(updated)
|
|
||||||
return data
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Load list from localStorage
|
* Load list from localStorage
|
||||||
*/
|
*/
|
||||||
|
@ -308,30 +234,22 @@ export class Set {
|
||||||
: this.items.findIndex(x => x.id == pred.id);
|
: this.items.findIndex(x => x.id == pred.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
extend(items, options) {
|
|
||||||
items.forEach(i => this.push(i, options))
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add item to set, return index.
|
* Add item to set, return index.
|
||||||
* If item already exists, replace it.
|
|
||||||
*/
|
*/
|
||||||
push(item, {args={},save=true}={}) {
|
push(item, {args={},save=true}={}) {
|
||||||
item = item instanceof this.model ? item : new this.model(item, args);
|
item = item instanceof this.model ? item : new this.model(item, args);
|
||||||
let index = -1
|
if(this.unique) {
|
||||||
if(this.unique && item.id) {
|
let index = this.findIndex(item);
|
||||||
index = this.findIndex(item);
|
|
||||||
if(index > -1)
|
if(index > -1)
|
||||||
this.items[index] = item
|
return index;
|
||||||
}
|
}
|
||||||
if(index == -1) {
|
if(this.max && this.items.length >= this.max)
|
||||||
if(this.max && this.items.length >= this.max)
|
this.items.splice(0,this.items.length-this.max)
|
||||||
this.items.splice(0,this.items.length-this.max)
|
|
||||||
this.items.push(item)
|
this.items.push(item);
|
||||||
index = this.items.length-1
|
save && this.save();
|
||||||
}
|
return this.items.length-1;
|
||||||
save && this.save()
|
|
||||||
return index;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -2,4 +2,6 @@ import "./styles/public.scss"
|
||||||
import './index.js'
|
import './index.js'
|
||||||
import App from './app.js'
|
import App from './app.js'
|
||||||
|
|
||||||
|
export default App
|
||||||
|
|
||||||
window.App = App
|
window.App = App
|
||||||
|
|
|
@ -309,9 +309,9 @@
|
||||||
.preview-header {
|
.preview-header {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|
||||||
/*&:not(.no-cover) {
|
&:not(.no-cover) {
|
||||||
min-height: var(--header-height);
|
min-height: var(--header-height);
|
||||||
}*/
|
}
|
||||||
|
|
||||||
&.no-cover {
|
&.no-cover {
|
||||||
height: unset;
|
height: unset;
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
@use "./vars" as v;
|
@use "./vars" as v;
|
||||||
|
|
||||||
// ---- text
|
// ---- text
|
||||||
.text-light { font-weight: 400; color: var(--text-color-light); }
|
.text-light { weight: 400; color: var(--text-color-light); }
|
||||||
|
|
||||||
.bigger { font-size: v.$text-size-bigger !important; }
|
.bigger { font-size: v.$text-size-bigger !important; }
|
||||||
.big { font-size: v.$text-size-big !important; }
|
.big { font-size: v.$text-size-big !important; }
|
||||||
|
@ -21,10 +21,6 @@
|
||||||
|
|
||||||
&.x { padding-right: 0px !important; }
|
&.x { padding-right: 0px !important; }
|
||||||
}
|
}
|
||||||
.align-center {
|
|
||||||
text-align: center !important;
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.clear-left { clear: left !important }
|
.clear-left { clear: left !important }
|
||||||
.clear-right { clear: right !important }
|
.clear-right { clear: right !important }
|
||||||
|
|
|
@ -398,7 +398,7 @@ nav li {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.header-cover:only-child {
|
.header-cover:only-child {
|
||||||
width: 100%;
|
with: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media screen and (max-width: v.$screen-small) {
|
@media screen and (max-width: v.$screen-small) {
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
@import 'v-calendar/style.css';
|
@import 'v-calendar/style.css';
|
||||||
// @import '@fortawesome/fontawesome-free/css/all.min.css';
|
@import '@fortawesome/fontawesome-free/css/all.min.css';
|
||||||
|
|
||||||
// ---- bulma
|
// ---- bulma
|
||||||
$body-color: #000;
|
$body-color: #000;
|
||||||
|
|
|
@ -1,44 +0,0 @@
|
||||||
import { resolve } from 'path'
|
|
||||||
import { fileURLToPath, URL } from 'node:url'
|
|
||||||
|
|
||||||
import { defineConfig } from 'vite'
|
|
||||||
import vue from '@vitejs/plugin-vue'
|
|
||||||
import commonjs from '@rollup/plugin-commonjs';
|
|
||||||
|
|
||||||
|
|
||||||
// https://vitejs.dev/config/
|
|
||||||
export default defineConfig({
|
|
||||||
plugins: [
|
|
||||||
vue(),
|
|
||||||
],
|
|
||||||
build: {
|
|
||||||
outDir: "../aircox/static/aircox/",
|
|
||||||
sourcemap: true,
|
|
||||||
|
|
||||||
rollupOptions: {
|
|
||||||
external: ['vue',],
|
|
||||||
input: {
|
|
||||||
public: "src/public.js",
|
|
||||||
admin: "src/admin.js",
|
|
||||||
},
|
|
||||||
output: {
|
|
||||||
globals: {
|
|
||||||
vue: 'Vue',
|
|
||||||
},
|
|
||||||
assetFileNames: "[name].[ext]",
|
|
||||||
chunkFileNames: "[name].js",
|
|
||||||
entryFileNames: "[name].js",
|
|
||||||
},
|
|
||||||
plugins: [commonjs()],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
css: {
|
|
||||||
devSourcemap: true,
|
|
||||||
},
|
|
||||||
resolve: {
|
|
||||||
extensions: ['.js', '.ts', '.json', '.vue'],
|
|
||||||
alias: {
|
|
||||||
'@': fileURLToPath(new URL('./src', import.meta.url))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
23
assets/vue.config.js
Normal file
23
assets/vue.config.js
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
const path = require('path');
|
||||||
|
|
||||||
|
const { defineConfig } = require('@vue/cli-service')
|
||||||
|
module.exports = defineConfig({
|
||||||
|
transpileDependencies: true,
|
||||||
|
outputDir: path.resolve('../aircox/static/aircox'),
|
||||||
|
publicPath: './',
|
||||||
|
runtimeCompiler: true,
|
||||||
|
filenameHashing: false,
|
||||||
|
|
||||||
|
css: {
|
||||||
|
extract: true,
|
||||||
|
loaderOptions: {
|
||||||
|
sass: { sourceMap: true },
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
pages: {
|
||||||
|
public: { entry: 'src/public.js' },
|
||||||
|
dashboard: { entry: 'src/dashboard.js' },
|
||||||
|
admin: { entry: 'src/admin.js' },
|
||||||
|
}
|
||||||
|
})
|
|
@ -254,8 +254,4 @@ WSGI_APPLICATION = "instance.wsgi.application"
|
||||||
LOGOUT_REDIRECT_URL = "/"
|
LOGOUT_REDIRECT_URL = "/"
|
||||||
|
|
||||||
|
|
||||||
REST_FRAMEWORK = {
|
REST_FRAMEWORK = {"DEFAULT_PAGINATION_CLASS": "rest_framework.pagination.LimitOffsetPagination", "PAGE_SIZE": 50}
|
||||||
"DEFAULT_PAGINATION_CLASS": "rest_framework.pagination.LimitOffsetPagination",
|
|
||||||
"PAGE_SIZE": 50,
|
|
||||||
"DEFAULT_FILTER_BACKENDS": ["django_filters.rest_framework.DjangoFilterBackend"],
|
|
||||||
}
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user