How do I serialize django-mptt trees in Tastypie?
I want to use django-mptt's cache_tree_children(). I've tried applying in different Tastypie hooks, but it throws an error.
Without the cache_tree_children method you could probably have your children serialized by simply hooking up a ToManyField with full=True pointing at the children property:
class MenuResource(ModelResource):
children = fields.ToManyField('self', 'children', null=True, full=True)
parent = fields.ToOneField('self', 'parent', null=True)
class Meta:
queryset = Menu.objects.all()
To implement the cache_tree_children function you could write your own ToManyField subclass that overrides the standard dehydrate function. Please note, that I only tested this solution very superficially:
def dehydrate(self, bundle):
if not bundle.obj or not bundle.obj.pk:
if not self.null:
raise ApiFieldError("The model '%r' does not have a primary key and can not be used in a ToMany context." % bundle.obj)
return []
the_m2ms = None
previous_obj = bundle.obj
attr = self.attribute
if isinstance(self.attribute, basestring):
attrs = self.attribute.split('__')
the_m2ms = bundle.obj
for attr in attrs:
previous_obj = the_m2ms
try:
the_m2ms = getattr(the_m2ms, attr, None)
except ObjectDoesNotExist:
the_m2ms = None
if not the_m2ms:
break
elif callable(self.attribute):
the_m2ms = self.attribute(bundle)
if not the_m2ms:
if not self.null:
raise ApiFieldError("The model '%r' has an empty attribute '%s' and doesn't allow a null value." % (previous_obj, attr))
return []
self.m2m_resources = []
m2m_dehydrated = []
# There goes your ``cache_tree_children``
for m2m in cache_tree_children(the_m2ms.all()):
m2m_resource = self.get_related_resource(m2m)
m2m_bundle = Bundle(obj=m2m, request=bundle.request)
self.m2m_resources.append(m2m_resource)
m2m_dehydrated.append(self.dehydrate_related(m2m_bundle, m2m_resource))
return m2m_dehydrated
One of the main advantages of this method is that you don't have to care about detail / list view constraints / differences no more. You even can parameterize this aspect of your resource further down until you got some kind of default behavior that fits your needs. Field-based, that is. Which I think is cool.
This is how I solved it:
class MenuResource(ModelResource):
parent = fields.ForeignKey('self', 'parent', null=True)
class Meta:
serializer = PrettyJSONSerializer()
queryset = Menu.objects.all().select_related('parent')
include_resource_uri = False
fields = ['name']
def get_child_data(self, obj):
data = {
'id': obj.id,
'name': obj.name,
}
if not obj.is_leaf_node():
data['children'] = [self.get_child_data(child) \
for child in obj.get_children()]
return data
def get_list(self, request, **kwargs):
base_bundle = self.build_bundle(request=request)
objects = self.obj_get_list(bundle=base_bundle,
**self.remove_api_resource_names(kwargs))
sorted_objects = self.apply_sorting(objects, options=request.GET)
paginator = self._meta.paginator_class(
request.GET, sorted_objects,
resource_uri=self.get_resource_uri(), limit=self._meta.limit,
max_limit=self._meta.max_limit,
collection_name=self._meta.collection_name
)
to_be_serialized = paginator.page()
from mptt.templatetags.mptt_tags import cache_tree_children
objects = cache_tree_children(objects)
bundles = []
for obj in objects:
data = self.get_child_data(obj)
bundle = self.build_bundle(data=data, obj=obj, request=request)
bundles.append(self.full_dehydrate(bundle))
to_be_serialized[self._meta.collection_name] = bundles
to_be_serialized = self.alter_list_data_to_serialize(request,
to_be_serialized)
return self.create_response(request, to_be_serialized)
If you're not using pagination you can just take that part out. That's what I did.
Related
I need help with REST Framework. I have to do my test for internship position and I have two models with circular import 'Pokemon' model and 'Team' mode. In serializer of 'Team' I have this code
class TeamDetailsSerializer(ModelSerializer):
"""Serializer for details of Team instances"""
pokemon_1 = SerializerMethodField()
pokemon_2 = SerializerMethodField()
pokemon_3 = SerializerMethodField()
pokemon_4 = SerializerMethodField()
pokemon_5 = SerializerMethodField()
trainer = UserSerializer()
class Meta:
model = Team
fields = (
"trainer",
"name",
"pokemon_1",
"pokemon_2",
"pokemon_3",
"pokemon_4",
"pokemon_5",
)
read_only_fields = ("id",)
# Methods to relate each Pokemon object
def get_pokemon_1(self, obj):
pokemon_1 = obj.pokemon_1
if not pokemon_1:
return None
serializer = pokemon.serializers.PokemonDetailsSerializer(pokemon_1)
return serializer.data
def get_pokemon_2(self, obj):
pokemon_2 = obj.pokemon_2
if not pokemon_2:
return None
serializer = pokemon.serializers.PokemonDetailsSerializer(pokemon_2)
return serializer.data
def get_pokemon_3(self, obj):
pokemon_3 = obj.pokemon_3
if not pokemon_3:
return None
serializer = pokemon.serializers.PokemonDetailsSerializer(pokemon_3)
return serializer.data
def get_pokemon_4(self, obj):
pokemon_4 = obj.pokemon_4
if not pokemon_4:
return None
serializer = pokemon.serializers.PokemonDetailsSerializer(pokemon_4)
return serializer.data
def get_pokemon_5(self, obj):
pokemon_5 = obj.pokemon_5
if not pokemon_5:
return None
serializer = pokemon.serializers.PokemonDetailsSerializer(pokemon_5)
return serializer.data
and problem that I get this kind of schema
name* [...]
trainer* User{...}
pokemon_1 integer
nullable: true
pokemon_2 [...]
pokemon_3 [...]
pokemon_4 [...]
pokemon_5 [...]
but I would like to get object type, what kind of solutions I can apply?
Thank a lot
I have a form like this,
class UniqueUrlForm(forms.ModelForm):
cc_number = cc_form.CardNumberField(label='Card Number')
cc_expiry = cc_form.CardExpiryField(label='Expiration Date')
cc_code = cc_form.SecurityCodeField(label='CVV/CVC')
class Meta:
model = Transactions
fields = ['customer_name', 'customer_phone', 'customer_email', 'total_amount', 'cc_number', 'cc_expiry',
'cc_code']
def __init__(self, *args, **kwargs):
super().__init__()
store_id = kwargs.get("store_id", "1")
payment_page = get_object_or_404(
PaymentPageDisplayDetails.objects.filter(store_id=store_id).values("payment_fields_visible"))
with urllib.request.urlopen(payment_page['payment_fields_visible']) as url:
display_fields = json.loads(url.read().decode())
for field_name in display_fields:
self.fields[field_name] = forms.CharField(required=False)
and a view like this,
def getpaymentpage(request, store_identifier):
uniqueurl_form = UniqueUrlForm(request.POST or None, request.FILES or None, {"store_id": 1})
if uniqueurl_form.is_valid():
trx_details = {
"amount": uniqueurl_form.cleaned_data['amount'],
"customer_email": uniqueurl_form.cleaned_data['customer_email'],
"customer_phone": uniqueurl_form.cleaned_data['customer_phone'],
"cc_number": uniqueurl_form.cleaned_data['cc_number'],
"cc_name": uniqueurl_form.cleaned_data['customer_name'],
"cc_month": uniqueurl_form.cleaned_data['cc_month'],
"cc_year": uniqueurl_form.cleaned_data['cc_year'],
"cvv": uniqueurl_form.cleaned_data['cvv'],
}
return HttpResponse(trx_details)
context = {
'form': {
uniqueurl_form,
},
"page": store_display,
}
return render(request, 'unique_url.html', context)
I have tried print(uniqueurl_form.errors) it always returns empty and uniqueurl_form.is_valid() as false.
Is it because I'm adding dynamic fields to the form.
I have referred the following,
dynamically add field to a form
What am I doing wrong here?
Thank you for your suggestions.
weirdly it started working when i made following changes,
class UniqueUrlForm(forms.ModelForm):
cc_number = cc_form.CardNumberField(label='Card Number')
cc_expiry = cc_form.CardExpiryField(label='Expiration Date')
cc_code = cc_form.SecurityCodeField(label='CVV/CVC')
class Meta:
model = Transactions
fields = ['customer_name', 'customer_phone', 'customer_email', 'total_amount', 'cc_number', 'cc_expiry',
'cc_code']
def __init__(self, *args, **kwargs):
store_id = kwargs.get("store_id", "1")
super(UniqueUrlForm, self).__init__(*args, **kwargs)
payment_page = get_object_or_404(
PaymentPageDisplayDetails.objects.filter(store_id=store_id).values("payment_fields_visible"))
with urllib.request.urlopen(payment_page['payment_fields_visible']) as url:
display_fields = json.loads(url.read().decode())
for field_name in display_fields:
self.fields[field_name] = forms.CharField()
my guess is because I did not specify class name in my .super() event though it was appending the fields it was not sure what validations to put on those fields.
I'm using Django 2 and Python 3.7. I have these models set up. One (Coop) is dependent on the other (CoopType) using Many-To-Many ...
class CoopTypeManager(models.Manager):
def get_by_natural_key(self, name):
return self.get_or_create(name=name)[0]
class CoopType(models.Model):
name = models.CharField(max_length=200, null=False, unique=True)
objects = CoopTypeManager()
class CoopManager(models.Manager):
# Look up by coop type
def get_by_type(self, type):
qset = Coop.objects.filter(type__name=type,
enabled=True)
return qset
# Look up coops by a partial name (case insensitive)
def find_by_name(self, partial_name):
queryset = Coop.objects.filter(name__icontains=partial_name, enabled=True)
print(queryset.query)
return queryset
# Meant to look up coops case-insensitively by part of a type
def contains_type(self, types_arr):
filter = Q(
*[('type__name__icontains', type) for type in types_arr],
_connector=Q.OR
)
queryset = Coop.objects.filter(filter,
enabled=True)
return queryset
class Coop(models.Model):
objects = CoopManager()
name = models.CharField(max_length=250, null=False)
types = models.ManyToManyField(CoopType)
address = AddressField(on_delete=models.CASCADE)
enabled = models.BooleanField(default=True, null=False)
phone = PhoneNumberField(null=True)
email = models.EmailField(null=True)
web_site = models.TextField()
I have the following serializers set up, designed to return the data in JSON form ...
class CoopTypeField(serializers.PrimaryKeyRelatedField):
queryset = CoopType.objects
def to_internal_value(self, data):
if type(data) == dict:
cooptype, created = CoopType.objects.get_or_create(**data)
# Replace the dict with the ID of the newly obtained object
data = cooptype.pk
return super().to_internal_value(data)
...
class CoopTypeSerializer(serializers.ModelSerializer):
class Meta:
model = CoopType
fields = ['id', 'name']
def create(self, validated_data):
"""
Create and return a new `CoopType` instance, given the validated data.
"""
return CoopType.objects.create(**validated_data)
def update(self, instance, validated_data):
"""
Update and return an existing `CoopType` instance, given the validated data.
"""
instance.name = validated_data.get('name', instance.name)
instance.save()
return instance
class CoopSerializer(serializers.ModelSerializer):
types = CoopTypeSerializer(many=True)
address = AddressTypeField()
class Meta:
model = Coop
fields = ['id', 'name', 'types', 'address', 'phone', 'enabled', 'email', 'web_site']
extra_kwargs = {
'phone': {
'required': False,
'allow_blank': True
}
}
def to_representation(self, instance):
rep = super().to_representation(instance)
rep['types'] = CoopTypeSerializer(instance.types).data
rep['address'] = AddressSerializer(instance.address).data
return rep
def create(self, validated_data):
#"""
#Create and return a new `Snippet` instance, given the validated data.
coop_types = validated_data.pop('types', {})
instance = super().create(validated_data)
for item in coop_types:
coop_type, _ = CoopType.objects.get_or_create(name=item['name']) #**item)
instance.types.add(coop_type)
return instance
However, the dependent field, "type" is always returned as "null," despite the fact that I can see there is valid data in the database. Here is what happens when I run my curl request
curl -v --header "Content-type: application/json" --request GET "http://127.0.0.1:8000/coops/?contains=resource"
[{"id":348,"name":"Garden Resources of Woodlawn (GRoW)","types":{"name":null}
How do I edit my serializer such that it returns the values of the dependent type?
Try to remove rep['types'] = CoopTypeSerializer(instance.types).data from to_representation(...) method,
def to_representation(self, instance):
rep = super().to_representation(instance)
rep['types'] = CoopTypeSerializer(instance.types).data
rep['address'] = AddressSerializer(instance.address).data
return rep
OR
use instance.types.all() instead of instance.types, because, here the instance.types is a Manager method, which doesn't return any QuerySet
def to_representation(self, instance):
rep = super().to_representation(instance)
rep['types'] = CoopTypeSerializer(instance.types.all(), many=True).data
rep['address'] = AddressSerializer(instance.address).data
return rep
I have a problem with filtering objects in View Set... I am trying to show objects only where field 'point' is null.
I always get error: NameError: name 'null' is not defined
Could you please HELP ME ?
My code:
class CompanyMapSerializer(serializers.ModelSerializer):
class Meta:
model = Company
fields = ('name', 'point', 'url', 'pk')
extra_kwargs = {
'url': {'view_name': 'api:company-detail'},
}
def to_representation(self, instance):
ret = super(CompanyMapSerializer, self).to_representation(instance)
ret['point'] = {
'latitude': instance.point.x,
'longitude': instance.point.y
}
return ret
And view set code:
class CompanyMapViewSet(viewsets.ModelViewSet):
queryset = Company.objects.filter(point = null)
serializer_class = CompanyMapSerializer
PageNumberPagination.page_size = 10000
Please help me.
You are not defining what null is, and Python doesn't recognize null as a primitive, you've got two options:
queryset = Company.objects.filter(point = None) # using None
queryset = Company.objects.filter(point__isnull = True) # explicitly asking for Null
These two queries are equally valid.
I have a update view for my model and this model has a many to many relation with another model. Due to some filtering requirements I created a custom multiplechoicefield and am saving it by modifying the form_valid. But if this multiple choice field is not selected form is getting posted to form_invalid. This field is not required. My view and form look as follows:
class Updatemodel(UpdateView):
template_name = ...
def get_success_url(self):
return ..
def get_from_class(self):
.. ..
def form_valid(self,form):
kwargs = super(Updatemodel, self).get_form_kwargs()
m2m_initial = kwargs['instance'].model_m2m.filter( .. )
chosen_m2m = model_m2m.objects.filter(pk__in = form.cleaned_data.get('model_m2m'))
m2m_add = chosen_m2m.exclude(pk__in = m2m_initial.values_list('pk, flat = True))
m2m_remove = m2m_initial.exclude(pk__in = chosen_m2m.values_list('pk,flat = True))
form.instance.model_m2m.add(*m2m_add)
form.instance.model_m2m.remove(*m2m_remove)
return super(Updatemodel, self).form_valid(form)
def get_form_kwargs(self):
kwargs = super(Updatemodel, self).get_form_kwargs()
kwargs['m2m_initial'] = kwargs['instance'].model_m2m.filter( ..)
kwargs['m2m'] = model_m2m.objects.all().filter( .. )
.................. form ......................
class m2m_update_form(forms.ModelForm):
def __init__(self, *args, **kwargs):
m2m = kwargs.pop('m2m',None)
m2m_initial = kwargs.pop('m2m_initial',None)
super(m2m_update_form, self).__init__(*args, **kwargs)
choices = [(M2M.pk, M2M.Name) for M2M in m2m ]
self.fields['m2m_field'] = forms.MultipleChoiceField(choices = choices, widget = FilteredSelectMultiple("M2M", False, choices = choices))
self.initial['m2m_field'] = [M2M for M2M in m2m_initial]
class Media:
..
class Meta:
model = model1
fields = ['field1', 'field2','field3']
widgets = {
'field3' = FilteredSelectMultiple("Field", False),
}
Would this solve your problem?
self.fields['m2m_field'] = forms.MultipleChoiceField(choices = choices,
widget = FilteredSelectMultiple("M2M", False, choices = choices),
required=False)