Add api-serializer

This commit is contained in:
Paul Konstantin Gerke 2022-02-15 22:24:13 +01:00
parent 56ebbd3181
commit 5046b754e5
6 changed files with 77 additions and 33 deletions

View File

@ -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},
}

View File

@ -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"

View File

@ -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",)

View File

@ -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={

View File

@ -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(

View File

@ -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 %}&quot;{{ t.name }}&quot;{% 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>