Skip to content

Commit

Permalink
feat: add colour and icon to Tags
Browse files Browse the repository at this point in the history
  • Loading branch information
pablolmedorado committed Oct 12, 2021
1 parent 10b4fbd commit 17a9291
Show file tree
Hide file tree
Showing 26 changed files with 374 additions and 53 deletions.
22 changes: 15 additions & 7 deletions backend/common/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,9 @@

from import_export.admin import ImportExportActionModelAdmin
from ordered_model.admin import OrderedModelAdmin
from taggit.models import Tag
from taggit.models import Tag as TaggitTag

from .behaviors import UUIDTaggedItem
from .models import Link, LinkType
from .models import Link, LinkType, Tag
from .resources import LinkResource, LinkTypeResource, TagResource


Expand Down Expand Up @@ -35,13 +34,22 @@ class LinkAdmin(ImportExportActionModelAdmin, OrderedModelAdmin):
resource_class = LinkResource


@admin.register(Tag)
class TagAdmin(ImportExportActionModelAdmin):
list_display = ("name", "slug")
ordering = ("name", "slug")
list_display = (
"name",
"slug",
"icon",
"colored_colour",
)
search_fields = ("name",)
ordering = ("name", "slug")
prepopulated_fields = {"slug": ("name",)}
fieldsets = (
(_("Información básica"), {"fields": ("name", "slug")}),
(_("Apariencia"), {"fields": ("colour", "icon")}),
)
resource_class = TagResource


admin.site.unregister(Tag)
admin.site.register(Tag, TagAdmin)
admin.site.unregister(TaggitTag)
3 changes: 2 additions & 1 deletion backend/common/api/filtersets.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
from django_filters.rest_framework import FilterSet

from notifications.models import Notification
from taggit.models import Tag

from ..models import Tag


class NotificationFilterSet(FilterSet):
Expand Down
5 changes: 3 additions & 2 deletions backend/common/api/serializers.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
from notifications.models import Notification
from rest_flex_fields import FlexFieldsModelSerializer
from rest_framework import serializers
from taggit.models import Tag

from ..models import Link, LinkType
from ..models import Link, LinkType, Tag
from users.api.serializers import UserSerializer


Expand Down Expand Up @@ -42,6 +41,8 @@ class Meta:
"id",
"name",
"slug",
"colour",
"icon",
)
read_only_fields = fields

Expand Down
3 changes: 1 addition & 2 deletions backend/common/api/viewsets.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
from rest_framework.decorators import action
from rest_framework.response import Response
from rest_framework.viewsets import GenericViewSet
from taggit.models import Tag

from .filtersets import NotificationFilterSet, TagFilterSet
from .mixins import (
Expand All @@ -16,7 +15,7 @@
)
from .permissions import NotificationPermission
from .serializers import LinkSerializer, NotificationSerializer, TagSerializer
from ..models import Link
from ..models import Link, Tag


class AtomicModelViewSet(
Expand Down
9 changes: 1 addition & 8 deletions backend/common/behaviors.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
from django.utils.translation import ugettext_lazy as _

from taggit.managers import TaggableManager
from taggit.models import GenericUUIDTaggedItemBase, TaggedItemBase


class Uuidable(models.Model):
Expand Down Expand Up @@ -84,14 +83,8 @@ class Meta:
abstract = True


class UUIDTaggedItem(GenericUUIDTaggedItemBase, TaggedItemBase):
class Meta:
verbose_name = _("Tag")
verbose_name_plural = _("Tags")


class Taggable(Uuidable):
tags = TaggableManager(through=UUIDTaggedItem, blank=True)
tags = TaggableManager(through="common.TaggedItem", blank=True)

class Meta:
abstract = True
117 changes: 117 additions & 0 deletions backend/common/migrations/0003_custom_tags.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
import colorfield.fields
from django.db import migrations, models
import django.db.models.deletion


def populate_custom_tags(apps, schema_editor):
# We can't import the models directly as it may be a newer
# version than this migration expects. We use the historical version.
TaggitTag = apps.get_model("taggit", "Tag")
TaggitTaggedItem = apps.get_model("common", "UUIDTaggedItem")

Tag = apps.get_model("common", "Tag")
TaggedItem = apps.get_model("common", "TaggedItem")

tags_dict = {}
for old_tag in TaggitTag.objects.all().order_by("id").iterator():
tag, created = Tag.objects.get_or_create(name=old_tag.name, slug=old_tag.slug)
tags_dict[tag.slug] = tag.pk

for old_tagged_item in TaggitTaggedItem.objects.select_related("tag").all().iterator():
TaggedItem.objects.get_or_create(
object_id=old_tagged_item.object_id,
content_type_id=old_tagged_item.content_type_id,
tag_id=tags_dict[old_tagged_item.tag.slug],
)


def revert_custom_tags(apps, schema_editor):
# We can't import the models directly as it may be a newer
# version than this migration expects. We use the historical version.
TaggitTag = apps.get_model("taggit", "Tag")
TaggitTaggedItem = apps.get_model("common", "UUIDTaggedItem")

Tag = apps.get_model("common", "Tag")
TaggedItem = apps.get_model("common", "TaggedItem")

tags_dict = {}
for tag in Tag.objects.all().order_by("id").iterator():
old_tag, created = TaggitTag.objects.get_or_create(name=tag.name, slug=tag.slug)
tags_dict[old_tag.slug] = old_tag.pk

for tagged_item in TaggedItem.objects.select_related("tag").all().iterator():
TaggitTaggedItem.objects.get_or_create(
object_id=tagged_item.object_id,
content_type_id=tagged_item.content_type_id,
tag_id=tags_dict[tagged_item.tag.slug],
)


class Migration(migrations.Migration):

dependencies = [
("contenttypes", "0002_remove_content_type_name"),
("common", "0002_links"),
]

operations = [
migrations.CreateModel(
name="Tag",
fields=[
("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")),
("slug", models.SlugField(max_length=100, unique=True, verbose_name="slug")),
(
"colour",
colorfield.fields.ColorField(
blank=True, default="#00AEC7", max_length=18, verbose_name="color en la aplicación"
),
),
(
"icon",
models.CharField(
blank=True,
default="mdi-label",
help_text="<a href='https://materialdesignicons.com/' target='_blank'>Material Design Icons</a>",
max_length=50,
verbose_name="icono en la aplicación",
),
),
],
options={
"verbose_name": "etiqueta",
"verbose_name_plural": "etiquetas",
},
),
migrations.CreateModel(
name="TaggedItem",
fields=[
("id", models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")),
("object_id", models.UUIDField(db_index=True, verbose_name="object ID")),
(
"content_type",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="common_taggeditem_tagged_items",
to="contenttypes.contenttype",
verbose_name="content type",
),
),
(
"tag",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="common_taggeditem_items",
to="common.tag",
),
),
],
options={
"verbose_name": "ítem etiquetado",
"verbose_name_plural": "ítems etiquetados",
"unique_together": {("content_type", "object_id", "tag")},
"index_together": {("content_type", "object_id")},
},
),
migrations.RunPython(populate_custom_tags, reverse_code=revert_custom_tags),
]
16 changes: 16 additions & 0 deletions backend/common/migrations/0004_delete_uuidtaggeditem_model.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
from django.db import migrations


class Migration(migrations.Migration):

dependencies = [
("events", "0005_alter_event_tags"),
("scrum", "0005_alter_tags_fields"),
("common", "0003_custom_tags"),
]

operations = [
migrations.DeleteModel(
name="UUIDTaggedItem",
),
]
41 changes: 40 additions & 1 deletion backend/common/models.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,41 @@
from django.db import models
from django.utils.html import format_html
from django.utils.translation import ugettext_lazy as _

from colorfield.fields import ColorField
from ordered_model.models import OrderedModel as Orderable
from taggit.models import GenericUUIDTaggedItemBase, TagBase


class Tag(TagBase):
colour = ColorField(_("color en la aplicación"), blank=True, default="#00AEC7")
icon = models.CharField(
_("icono en la aplicación"),
help_text="<a href='https://materialdesignicons.com/' target='_blank'>Material Design Icons</a>",
max_length=50,
blank=True,
default="mdi-label",
)

def colored_colour(self):
return format_html('<span style="color: {colour};">{colour}</span>', colour=self.colour)

colored_colour.short_description = _("Color") # type: ignore
colored_colour.admin_order_field = "colour" # type: ignore

class Meta:
verbose_name = _("etiqueta")
verbose_name_plural = _("etiquetas")


class TaggedItem(GenericUUIDTaggedItemBase):
tag = models.ForeignKey(Tag, related_name="%(app_label)s_%(class)s_items", on_delete=models.CASCADE)

class Meta:
verbose_name = _("ítem etiquetado")
verbose_name_plural = _("ítems etiquetados")
index_together = [["content_type", "object_id"]]
unique_together = [["content_type", "object_id", "tag"]]


class LinkType(Orderable, models.Model):
Expand All @@ -25,7 +59,12 @@ class Link(Orderable, models.Model):
)
url = models.URLField(_("url"), max_length=2000, blank=False, unique=True)
type = models.ForeignKey(
LinkType, verbose_name=_("tipo"), related_name="links", blank=False, null=False, on_delete=models.PROTECT,
LinkType,
verbose_name=_("tipo"),
related_name="links",
blank=False,
null=False,
on_delete=models.PROTECT,
)

order_with_respect_to = ("type",)
Expand Down
5 changes: 2 additions & 3 deletions backend/common/resources.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
from import_export import resources
from import_export.fields import Field
from import_export.widgets import ForeignKeyWidget
from taggit.models import Tag

from .models import Link, LinkType
from .models import Link, LinkType, Tag


class LinkResource(resources.ModelResource):
Expand All @@ -25,5 +24,5 @@ class Meta:
class TagResource(resources.ModelResource):
class Meta:
model = Tag
fields = ("id", "name", "slug")
fields = ("id", "name", "slug", "colour", "icon")
export_order = fields
6 changes: 4 additions & 2 deletions backend/events/api/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,12 @@

from rest_flex_fields import FlexFieldsModelSerializer
from rest_framework import serializers
from taggit_serializer.serializers import TagListSerializerField, TaggitSerializer
from taggit.serializers import TagListSerializerField, TaggitSerializer

from users.api.serializers import GroupSerializer, UserSerializer

from ..models import Event, EventType
from common.api.serializers import TagSerializer
from users.api.serializers import GroupSerializer, UserSerializer


class EventTypeSerializer(FlexFieldsModelSerializer):
Expand Down Expand Up @@ -63,4 +64,5 @@ class Meta:
"type": EventTypeSerializer,
"attendees": (UserSerializer, {"many": True}),
"groups": (GroupSerializer, {"many": True}),
"tags": (TagSerializer, {"many": True, "fields": ["name", "colour", "icon"]}),
}
2 changes: 1 addition & 1 deletion backend/events/api/viewsets.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ class EventTypeViewSet(AtomicFlexFieldsModelViewSet):
class EventViewSet(AuthorshipMixin, AtomicFlexFieldsModelViewSet):
permission_classes = (permissions.IsAuthenticated, IsOwnerOrReadOnly)
serializer_class = EventSerializer
permit_list_expands = ["attendees", "type", "groups"]
permit_list_expands = ["attendees", "type", "groups", "tags"]
filterset_class = EventFilterSet
search_fields = ("name", "details")
ordering_fields = ("name", "type", "start_datetime", "visibility")
Expand Down
24 changes: 24 additions & 0 deletions backend/events/migrations/0005_alter_event_tags.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
from django.db import migrations
import taggit.managers


class Migration(migrations.Migration):

dependencies = [
("common", "0003_custom_tags"),
("events", "0004_eventtype_system_slug"),
]

operations = [
migrations.AlterField(
model_name="event",
name="tags",
field=taggit.managers.TaggableManager(
blank=True,
help_text="A comma-separated list of tags.",
through="common.TaggedItem",
to="common.Tag",
verbose_name="Tags",
),
),
]
Loading

0 comments on commit 17a9291

Please sign in to comment.