mirror of https://github.com/snachodog/mybuddy.git
Add api-serializer
This commit is contained in:
parent
56ebbd3181
commit
5046b754e5
|
@ -202,3 +202,13 @@ class BMISerializer(CoreModelSerializer):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = models.BMI
|
model = models.BMI
|
||||||
fields = ("id", "child", "bmi", "date", "notes")
|
fields = ("id", "child", "bmi", "date", "notes")
|
||||||
|
|
||||||
|
class TagsSerializer(serializers.HyperlinkedModelSerializer):
|
||||||
|
class Meta:
|
||||||
|
model = models.BabyBuddyTag
|
||||||
|
fields = ("slug", "name", "color", "last_used")
|
||||||
|
extra_kwargs = {
|
||||||
|
"slug": {"required": False, "read_only": True},
|
||||||
|
"color": {"required": False},
|
||||||
|
"last_used": {"required": False, "read_only": True},
|
||||||
|
}
|
||||||
|
|
|
@ -18,6 +18,7 @@ router.register(r"weight", views.WeightViewSet)
|
||||||
router.register(r"height", views.HeightViewSet)
|
router.register(r"height", views.HeightViewSet)
|
||||||
router.register(r"head-circumference", views.HeadCircumferenceViewSet)
|
router.register(r"head-circumference", views.HeadCircumferenceViewSet)
|
||||||
router.register(r"bmi", views.BMIViewSet)
|
router.register(r"bmi", views.BMIViewSet)
|
||||||
|
router.register(r"tags", views.TagsViewSet)
|
||||||
|
|
||||||
app_name = "api"
|
app_name = "api"
|
||||||
|
|
||||||
|
|
|
@ -91,3 +91,10 @@ class BMIViewSet(viewsets.ModelViewSet):
|
||||||
queryset = models.BMI.objects.all()
|
queryset = models.BMI.objects.all()
|
||||||
serializer_class = serializers.BMISerializer
|
serializer_class = serializers.BMISerializer
|
||||||
filterset_fields = ("child", "date")
|
filterset_fields = ("child", "date")
|
||||||
|
|
||||||
|
class TagsViewSet(viewsets.ModelViewSet):
|
||||||
|
queryset = models.BabyBuddyTag.objects.all()
|
||||||
|
serializer_class = serializers.TagsSerializer
|
||||||
|
lookup_field = "slug"
|
||||||
|
filterset_fields = ("last_used",)
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
# Generated by Django 4.0.2 on 2022-02-15 14:43
|
# Generated by Django 4.0.2 on 2022-02-15 16:33
|
||||||
|
|
||||||
import core.models
|
import core.models
|
||||||
|
import django.core.validators
|
||||||
from django.db import migrations, models
|
from django.db import migrations, models
|
||||||
import django.db.models.deletion
|
import django.db.models.deletion
|
||||||
import django.utils.timezone
|
import django.utils.timezone
|
||||||
|
@ -21,7 +22,7 @@ class Migration(migrations.Migration):
|
||||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
('name', models.CharField(max_length=100, unique=True, verbose_name='name')),
|
('name', models.CharField(max_length=100, unique=True, verbose_name='name')),
|
||||||
('slug', models.SlugField(max_length=100, unique=True, verbose_name='slug')),
|
('slug', models.SlugField(max_length=100, unique=True, verbose_name='slug')),
|
||||||
('color', models.CharField(default='#7F7F7F', max_length=32, validators=[core.models.validate_html_color], verbose_name='Color')),
|
('color', models.CharField(default=core.models.random_color, max_length=32, validators=[django.core.validators.RegexValidator('^#[0-9A-F]{6}$')], verbose_name='Color')),
|
||||||
('last_used', models.DateTimeField(default=django.utils.timezone.now)),
|
('last_used', models.DateTimeField(default=django.utils.timezone.now)),
|
||||||
],
|
],
|
||||||
options={
|
options={
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
import re
|
import re
|
||||||
|
import time
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
@ -11,10 +12,15 @@ from django.utils import timezone
|
||||||
from django.utils.text import format_lazy
|
from django.utils.text import format_lazy
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
from django.utils.timezone import now
|
from django.utils.timezone import now
|
||||||
|
from django.core.validators import RegexValidator
|
||||||
|
|
||||||
|
import random
|
||||||
|
|
||||||
from taggit.managers import TaggableManager
|
from taggit.managers import TaggableManager
|
||||||
from taggit.models import TagBase, GenericTaggedItemBase, TaggedItemBase
|
from taggit.models import TagBase, GenericTaggedItemBase, TaggedItemBase
|
||||||
|
|
||||||
|
random.seed(time.time())
|
||||||
|
|
||||||
def validate_date(date, field_name):
|
def validate_date(date, field_name):
|
||||||
"""
|
"""
|
||||||
Confirm that a date is not in the future.
|
Confirm that a date is not in the future.
|
||||||
|
@ -74,8 +80,14 @@ def validate_time(time, field_name):
|
||||||
{field_name: _("Date/time can not be in the future.")}, code="time_invalid"
|
{field_name: _("Date/time can not be in the future.")}, code="time_invalid"
|
||||||
)
|
)
|
||||||
|
|
||||||
def validate_html_color(s: str):
|
TAG_COLORS = [
|
||||||
return re.match(r"^#[0-9A-F]{6}$", s) is not None
|
"#FF0000", "#00FF00", "#0000FF", "#FF00FF", "#FFFF00", "#00FFFF",
|
||||||
|
"#FF7F7F", "#7FFF7F", "#7F7FFF", "#FF7FFF", "#FFFF7F", "#7FFFFF",
|
||||||
|
"#7F0000", "#007F00", "#00007F", "#7F007F", "#7F7F00", "#007F7F",
|
||||||
|
]
|
||||||
|
|
||||||
|
def random_color():
|
||||||
|
return TAG_COLORS[random.randrange(0, len(TAG_COLORS))]
|
||||||
|
|
||||||
class BabyBuddyTag(TagBase):
|
class BabyBuddyTag(TagBase):
|
||||||
class Meta:
|
class Meta:
|
||||||
|
@ -85,8 +97,8 @@ class BabyBuddyTag(TagBase):
|
||||||
color = models.CharField(
|
color = models.CharField(
|
||||||
"Color",
|
"Color",
|
||||||
max_length=32,
|
max_length=32,
|
||||||
default="#7F7F7F",
|
default=random_color,
|
||||||
validators=[validate_html_color]
|
validators=[RegexValidator(r"^#[0-9A-F]{6}$")]
|
||||||
)
|
)
|
||||||
|
|
||||||
last_used = models.DateTimeField(
|
last_used = models.DateTimeField(
|
||||||
|
|
|
@ -22,13 +22,17 @@
|
||||||
<span>Suggestions:</span>
|
<span>Suggestions:</span>
|
||||||
{% for t in widget.tag_suggestions.quick %}
|
{% for t in widget.tag_suggestions.quick %}
|
||||||
<span data-value="{{ t.name }}" data-color="{{ t.color }}" class="tag btn badge badge-pill cursor-pointer" style="background-color: {{ t.color }};">
|
<span data-value="{{ t.name }}" data-color="{{ t.color }}" class="tag btn badge badge-pill cursor-pointer" style="background-color: {{ t.color }};">
|
||||||
{% if not forloop.first %},{% endif %}
|
|
||||||
{{ t.name }}
|
{{ t.name }}
|
||||||
<span class="add-remove-icon pl-1 pr-1">+</span>
|
<span class="add-remove-icon pl-1 pr-1">+</span>
|
||||||
</span>
|
</span>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
<script>
|
<input
|
||||||
|
type="hidden"
|
||||||
|
name="tags"
|
||||||
|
value="{% for t in widget.value %}"{{ t.name }}"{% if not forloop.last %},{% endif %}{% endfor %}"
|
||||||
|
>
|
||||||
|
<script>
|
||||||
window.addEventListener('load', () => {
|
window.addEventListener('load', () => {
|
||||||
const widget = document.getElementById('{{ widget.attrs.id }}');
|
const widget = document.getElementById('{{ widget.attrs.id }}');
|
||||||
|
|
||||||
|
@ -38,12 +42,39 @@
|
||||||
|
|
||||||
const inputElement = widget.querySelector('input');
|
const inputElement = widget.querySelector('input');
|
||||||
|
|
||||||
|
function hexParse(x) {
|
||||||
|
return parseInt(x, 16);
|
||||||
|
}
|
||||||
|
|
||||||
|
function computeComplementaryColor(colorStr) {
|
||||||
|
let avgColor = 0.0;
|
||||||
|
avgColor += hexParse(colorStr.substring(1, 3)) * -0.5;
|
||||||
|
avgColor += hexParse(colorStr.substring(3, 5)) * 1.5;
|
||||||
|
avgColor += hexParse(colorStr.substring(5, 7)) * 1.0;
|
||||||
|
|
||||||
|
console.log(`C ${colorStr} -> ${avgColor}`);
|
||||||
|
|
||||||
|
if (avgColor > 200) {
|
||||||
|
return "#101010";
|
||||||
|
} else {
|
||||||
|
return "#E0E0E0";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function updateTag(tag, name, color, actionSymbol) {
|
function updateTag(tag, name, color, actionSymbol) {
|
||||||
|
const actionTextNode = tag.querySelector('.add-remove-icon').childNodes[0];
|
||||||
|
|
||||||
|
name = name || tag.getAttribute("data-value");
|
||||||
|
color = color || tag.getAttribute("data-color");
|
||||||
|
actionSymbol = actionSymbol || actionTextNode.textContent;
|
||||||
|
|
||||||
tag.childNodes[0].textContent = name;
|
tag.childNodes[0].textContent = name;
|
||||||
tag.setAttribute("data-value", name);
|
tag.setAttribute("data-value", name);
|
||||||
tag.setAttribute("data-color", color);
|
tag.setAttribute("data-color", color);
|
||||||
tag.setAttribute('style', `background-color: ${color};`);
|
|
||||||
tag.querySelector('.add-remove-icon').childNodes[0].textContent = actionSymbol;
|
const textColor = computeComplementaryColor(color);
|
||||||
|
tag.setAttribute('style', `background-color: ${color}; color: ${textColor};`);
|
||||||
|
actionTextNode.textContent = actionSymbol;
|
||||||
}
|
}
|
||||||
|
|
||||||
function createNewTag(name, color, actionSymbol) {
|
function createNewTag(name, color, actionSymbol) {
|
||||||
|
@ -52,28 +83,12 @@
|
||||||
return tag;
|
return tag;
|
||||||
}
|
}
|
||||||
|
|
||||||
function addTagCallback(event) {
|
|
||||||
const tag = event.target;
|
|
||||||
newTags.removeChild(tag);
|
|
||||||
updateTag(
|
|
||||||
tag,
|
|
||||||
tag.getAttribute("data-value"),
|
|
||||||
tag.getAttribute("data-color"),
|
|
||||||
"-"
|
|
||||||
)
|
|
||||||
currentTags.appendChild(tag);
|
|
||||||
|
|
||||||
tag.removeEventListener('click', addTagCallback);
|
|
||||||
tag.addEventListener('click', removeTagCallback);
|
|
||||||
}
|
|
||||||
|
|
||||||
function registerNewCallback(tag, newParent, newSymbol, onClicked) {
|
function registerNewCallback(tag, newParent, newSymbol, onClicked) {
|
||||||
console.log(tag);
|
|
||||||
function callback(event) {
|
function callback(event) {
|
||||||
tag.parentNode.removeChild(tag);
|
tag.parentNode.removeChild(tag);
|
||||||
updateTag(
|
updateTag(
|
||||||
tag,
|
tag,
|
||||||
tag.getAttribute("data-value"),
|
null,
|
||||||
tag.getAttribute("data-color"),
|
tag.getAttribute("data-color"),
|
||||||
newSymbol
|
newSymbol
|
||||||
);
|
);
|
||||||
|
@ -88,7 +103,8 @@
|
||||||
function updateInputList() {
|
function updateInputList() {
|
||||||
const names = [];
|
const names = [];
|
||||||
for (const tag of currentTags.querySelectorAll(".tag")) {
|
for (const tag of currentTags.querySelectorAll(".tag")) {
|
||||||
names.push(tag.getAttribute("data-value"));
|
const name = tag.getAttribute("data-value");
|
||||||
|
names.push(`"${name}"`);
|
||||||
}
|
}
|
||||||
inputElement.value = names.join(",");
|
inputElement.value = names.join(",");
|
||||||
}
|
}
|
||||||
|
@ -104,16 +120,13 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const tag of newTags.querySelectorAll(".tag")) {
|
for (const tag of newTags.querySelectorAll(".tag")) {
|
||||||
|
updateTag(tag);
|
||||||
addTagCallback(tag);
|
addTagCallback(tag);
|
||||||
}
|
}
|
||||||
for (const tag of currentTags.querySelectorAll(".tag")) {
|
for (const tag of currentTags.querySelectorAll(".tag")) {
|
||||||
|
updateTag(tag);
|
||||||
removeTagCallback(tag);
|
removeTagCallback(tag);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
<input
|
|
||||||
type="hidden"
|
|
||||||
name="tags"
|
|
||||||
value="{% for t in widget.value %}{{ t.name }}{% endfor %}"
|
|
||||||
>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
Loading…
Reference in New Issue