-
-
Notifications
You must be signed in to change notification settings - Fork 621
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #738 from jazzband/serializer-vendoring
Vendor in `django-taggit-serializer`
- Loading branch information
Showing
10 changed files
with
368 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
Usage With Django Rest Framework | ||
================================ | ||
|
||
Because the tags in `django-taggit` need to be added into a `TaggableManager()` we cannot use the usual `Serializer` that we get from Django REST Framework. Because this is trying to save the tags into a `list`, which will throw an exception. | ||
|
||
To accept tags through a `REST` API call we need to add the following to our `Serializer`:: | ||
|
||
|
||
from taggit.serializers import (TagListSerializerField, | ||
TaggitSerializer) | ||
|
||
|
||
class YourSerializer(TaggitSerializer, serializers.ModelSerializer): | ||
|
||
tags = TagListSerializerField() | ||
|
||
class Meta: | ||
model = YourModel | ||
|
||
And you're done, so now you can add tags to your model. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,123 @@ | ||
""" | ||
Django-taggit serializer support | ||
Originally vendored from https://github.com/glemmaPaul/django-taggit-serializer | ||
""" | ||
import json | ||
|
||
# Third party | ||
from django.utils.translation import ugettext_lazy as _ | ||
from rest_framework import serializers | ||
|
||
|
||
class TagList(list): | ||
def __init__(self, *args, **kwargs): | ||
pretty_print = kwargs.pop("pretty_print", True) | ||
list.__init__(self, *args, **kwargs) | ||
self.pretty_print = pretty_print | ||
|
||
def __add__(self, rhs): | ||
return TagList(list.__add__(self, rhs)) | ||
|
||
def __getitem__(self, item): | ||
result = list.__getitem__(self, item) | ||
try: | ||
return TagList(result) | ||
except TypeError: | ||
return result | ||
|
||
def __str__(self): | ||
if self.pretty_print: | ||
return json.dumps(self, sort_keys=True, indent=4, separators=(",", ": ")) | ||
else: | ||
return json.dumps(self) | ||
|
||
|
||
class TagListSerializerField(serializers.Field): | ||
child = serializers.CharField() | ||
default_error_messages = { | ||
"not_a_list": _('Expected a list of items but got type "{input_type}".'), | ||
"invalid_json": _( | ||
"Invalid json list. A tag list submitted in string" | ||
" form must be valid json." | ||
), | ||
"not_a_str": _("All list items must be of string type."), | ||
} | ||
order_by = None | ||
|
||
def __init__(self, **kwargs): | ||
pretty_print = kwargs.pop("pretty_print", True) | ||
|
||
style = kwargs.pop("style", {}) | ||
kwargs["style"] = {"base_template": "textarea.html"} | ||
kwargs["style"].update(style) | ||
|
||
super(TagListSerializerField, self).__init__(**kwargs) | ||
|
||
self.pretty_print = pretty_print | ||
|
||
def to_internal_value(self, value): | ||
if isinstance(value, str): | ||
if not value: | ||
value = "[]" | ||
try: | ||
value = json.loads(value) | ||
except ValueError: | ||
self.fail("invalid_json") | ||
|
||
if not isinstance(value, list): | ||
self.fail("not_a_list", input_type=type(value).__name__) | ||
|
||
for s in value: | ||
if not isinstance(s, str): | ||
self.fail("not_a_str") | ||
|
||
self.child.run_validation(s) | ||
|
||
return value | ||
|
||
def to_representation(self, value): | ||
if not isinstance(value, TagList): | ||
if not isinstance(value, list): | ||
if self.order_by: | ||
tags = value.all().order_by(*self.order_by) | ||
else: | ||
tags = value.all() | ||
value = [tag.name for tag in tags] | ||
value = TagList(value, pretty_print=self.pretty_print) | ||
|
||
return value | ||
|
||
|
||
class TaggitSerializer(serializers.Serializer): | ||
def create(self, validated_data): | ||
to_be_tagged, validated_data = self._pop_tags(validated_data) | ||
|
||
tag_object = super(TaggitSerializer, self).create(validated_data) | ||
|
||
return self._save_tags(tag_object, to_be_tagged) | ||
|
||
def update(self, instance, validated_data): | ||
to_be_tagged, validated_data = self._pop_tags(validated_data) | ||
|
||
tag_object = super(TaggitSerializer, self).update(instance, validated_data) | ||
|
||
return self._save_tags(tag_object, to_be_tagged) | ||
|
||
def _save_tags(self, tag_object, tags): | ||
for key in tags.keys(): | ||
tag_values = tags.get(key) | ||
getattr(tag_object, key).set(*tag_values) | ||
|
||
return tag_object | ||
|
||
def _pop_tags(self, validated_data): | ||
to_be_tagged = {} | ||
|
||
for key in self.fields.keys(): | ||
field = self.fields[key] | ||
if isinstance(field, TagListSerializerField): | ||
if key in validated_data: | ||
to_be_tagged[key] = validated_data.pop(key) | ||
|
||
return (to_be_tagged, validated_data) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,126 @@ | ||
# Generated by Django 3.2.4 on 2021-06-19 08:26 | ||
|
||
import django.db.models.deletion | ||
from django.db import migrations, models | ||
|
||
import taggit.managers | ||
|
||
|
||
class Migration(migrations.Migration): | ||
|
||
dependencies = [ | ||
("contenttypes", "0002_remove_content_type_name"), | ||
("taggit", "0003_taggeditem_add_unique_index"), | ||
("tests", "0003_auto_20210310_0918"), | ||
] | ||
|
||
operations = [ | ||
migrations.AlterField( | ||
model_name="officialtag", | ||
name="name", | ||
field=models.CharField(max_length=100, unique=True, verbose_name="name"), | ||
), | ||
migrations.AlterField( | ||
model_name="officialtag", | ||
name="slug", | ||
field=models.SlugField(max_length=100, unique=True, verbose_name="slug"), | ||
), | ||
migrations.AlterField( | ||
model_name="officialthroughmodel", | ||
name="content_type", | ||
field=models.ForeignKey( | ||
on_delete=django.db.models.deletion.CASCADE, | ||
related_name="tests_officialthroughmodel_tagged_items", | ||
to="contenttypes.contenttype", | ||
verbose_name="content type", | ||
), | ||
), | ||
migrations.AlterField( | ||
model_name="officialthroughmodel", | ||
name="object_id", | ||
field=models.IntegerField(db_index=True, verbose_name="object ID"), | ||
), | ||
migrations.AlterField( | ||
model_name="taggedcustompk", | ||
name="content_type", | ||
field=models.ForeignKey( | ||
on_delete=django.db.models.deletion.CASCADE, | ||
related_name="tests_taggedcustompk_tagged_items", | ||
to="contenttypes.contenttype", | ||
verbose_name="content type", | ||
), | ||
), | ||
migrations.AlterField( | ||
model_name="throughgfk", | ||
name="content_type", | ||
field=models.ForeignKey( | ||
on_delete=django.db.models.deletion.CASCADE, | ||
related_name="tests_throughgfk_tagged_items", | ||
to="contenttypes.contenttype", | ||
verbose_name="content type", | ||
), | ||
), | ||
migrations.AlterField( | ||
model_name="throughgfk", | ||
name="object_id", | ||
field=models.IntegerField(db_index=True, verbose_name="object ID"), | ||
), | ||
migrations.AlterField( | ||
model_name="trackedtag", | ||
name="name", | ||
field=models.CharField(max_length=100, unique=True, verbose_name="name"), | ||
), | ||
migrations.AlterField( | ||
model_name="trackedtag", | ||
name="slug", | ||
field=models.SlugField(max_length=100, unique=True, verbose_name="slug"), | ||
), | ||
migrations.AlterField( | ||
model_name="uuidtag", | ||
name="name", | ||
field=models.CharField(max_length=100, unique=True, verbose_name="name"), | ||
), | ||
migrations.AlterField( | ||
model_name="uuidtag", | ||
name="slug", | ||
field=models.SlugField(max_length=100, unique=True, verbose_name="slug"), | ||
), | ||
migrations.AlterField( | ||
model_name="uuidtaggeditem", | ||
name="content_type", | ||
field=models.ForeignKey( | ||
on_delete=django.db.models.deletion.CASCADE, | ||
related_name="tests_uuidtaggeditem_tagged_items", | ||
to="contenttypes.contenttype", | ||
verbose_name="content type", | ||
), | ||
), | ||
migrations.AlterField( | ||
model_name="uuidtaggeditem", | ||
name="object_id", | ||
field=models.UUIDField(db_index=True, verbose_name="object ID"), | ||
), | ||
migrations.CreateModel( | ||
name="TestModel", | ||
fields=[ | ||
( | ||
"id", | ||
models.AutoField( | ||
auto_created=True, | ||
primary_key=True, | ||
serialize=False, | ||
verbose_name="ID", | ||
), | ||
), | ||
( | ||
"tags", | ||
taggit.managers.TaggableManager( | ||
help_text="A comma-separated list of tags.", | ||
through="taggit.TaggedItem", | ||
to="taggit.Tag", | ||
verbose_name="Tags", | ||
), | ||
), | ||
], | ||
), | ||
] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
import django | ||
from rest_framework import serializers | ||
|
||
from taggit.serializers import TaggitSerializer, TagListSerializerField | ||
|
||
from .models import TestModel | ||
|
||
|
||
class TestModelSerializer(TaggitSerializer, serializers.ModelSerializer): | ||
|
||
tags = TagListSerializerField() | ||
|
||
class Meta: | ||
model = TestModel | ||
if django.VERSION >= (1, 11): | ||
fields = "__all__" |
Oops, something went wrong.