Unable to use natural keys in django foreign key serialization - django

So the thing is I have a class which has a foreign key.
This is my code
class Proxy(models.Model):
class Meta:
db_table = 'Proxy'
equipment = models.ForeignKey('Equipment', primary_key=True)
pop = models.ForeignKey('Pop')
Now, as usual when I do
import django.core.serializers as Serializer
res = Proxy.objects.filter(equipment_id__exact='eq1')
Serializer.serialize('json', res)
the json output contains the "id" of the Pop and not the name, which I want.
So I used the Manager class and this is my Pop class now-
class PopManager(models.Manager):
def get_by_natural_key(self, name):
return self.get(name=name)
class Pop(models.Model):
POP_TYPES = (
('phy','phy'),
('cloud','cloud'),
)
class Meta:
db_table = 'Pop'
unique_together = ('name', 'vip')
objects = PopManager()
name = models.CharField(max_length=10)
type = models.CharField(max_length=10, choices=POP_TYPES)
def natural_key(self):
return (self.name)
But after this, when I do
res = Proxy.objects.filter(equipment_id__exact='eq1')
Serializer.serialize('json', res, use_natural_keys=True)
I get an error,
TypeError: Equipment: eq1 is not JSON serializable
I have also tried wadofstuff for this serialization of foreign keys, but apparently in Django1.5, there is a clash between simplejson and json, and the query was throwing an error. So I am back to square one.
Any help will be highly appereciated. I have been splitting my hair for hours.

Anyway, so I solved this by converting the QuerySet to a raw dictionary type format. I appended a values clause after filter. And looped through the entire queryset returned to make a list of dictionaries. The rest was easy.

Agniva,
You just need to delete your line:
db_table = 'Pop'

Related

Django rest framework: Foreign key instance is not being passed to validated_data

I'm trying to save data to my database using ModelSerializer but when I pass a ForeignKey instance, it gets converted to string or integer type from instance type after calling is_valid() and this is the error I get:
ValueError at /app/core/create-trip
Cannot assign "'ChIJTWE_0BtawokRVJNGH5RS448'": "CityAttrRtTb.attr_nm" must be a "CityAttrn" instance.
The error above shows that the CityAttrn instance was converted to its respective field value(and thus its type changed) after calling is_valid().
My problem is basically the same as this question
The question I linked to above has an answer that I need to pass the foreign key as an argument to the save() method of the serializer only after calling the is_valid() method.
if serializer.is_valid():
serializer.save(fk=foreign_key_instance)
In this example, I have one data and since I passed the foreign key after is_valid is called, there seems to be no problem. I can do that if I only have one data. However, I am trying to save multiple data at once with .save(many=True) and each of those data have a different foreign key instance. How do I save those kinds of data?
models.py
class CityAttraction(models.Model):
cty_attr_id = models.AutoField(primary_key=True)
cty_nm = models.CharField(max_length=200)
attr_nm = models.CharField(max_length=200, unique=True)
des = models.CharField(max_length=200, blank=True, null=True)
class Meta:
managed = False
db_table = 'city_attrn'
unique_together = (('cty_nm', 'attr_nm'),)
class CityAttractionRating(models.Model):
cty_nm = models.CharField(max_length=200)
attr_nm = models.ForeignKey('CityAttrn', on_delete=models.CASCADE, db_column='attr_nm', primary_key=True, to_field='attr_nm')
rt_src = models.IntegerField(blank=True, null=True)
class Meta:
managed = False
db_table = 'city_attr_rt_tb'
unique_together = (('cty_nm', 'attr_nm', 'rt_src'),)
views.py
def some_calculation(city_attraction):
final_result = []
for cityattraction_instance in city_attraction:
print(attraction)
rating_dict["cty_nm"] = "TEST"
rating_dict["attr_nm"] = cityattraction_instance # its type is CityAttraction instance right now but it gets changed to string after calling is_valid()
rating_dict["rt_src"] = 1
final_result.append(rating_dict.copy())
return final_result
city_attraction = CityAttractionSerializer(data=request.data, many=True) if city_attraction.is_valid():
city_attraction = city_attraction.save()
calculated_data = some_calculations(city_attraction)
integer_rated_attraction = CityAttractionRatingSerializer(data=calculated_data, many=True)
if integer_rated_attraction.is_valid():
integer_rated_attraction.save() #how do I save all the instances of CityAttraction here without changing its type to string or integer from "instance"?
In the above view, city_attraction is the list of all CityAttraction instances. I need to pass it to the arguments of integer_rated_attraction.save() so as to not lose the "instance" type because of is_valid(). How do I do it?
serializers.py
class CityAttractionSerializer(serializers.ModelSerializer):
class Meta:
model = CityAttraction
fields = '__all__'
class CityAttractionRtSerializer(serializers.ModelSerializer):
class Meta:
model = CityAttractionRating
fields = '__all__'
When I pass in the CityAttraction instance to some_calculation function and then performing the validation and saving, I was expecting the model to be saved. But, because calling the is_valid() function changed the CityAttr to the field value("ChIJTWE_0BtawokRVJNGH5RS448" in this case), I got an Value Error exception shown at the top. How would I go about solving this? Thank you!
You have mistake in save.
serializer.save(fk=foreign_key_instance) =>
serializer.save(pk=foreign_key_instance)

Django Converting Queryset with Foreign Keys to JSON

I'm trying to pass JSON objects to my template. I have some complicated relationships that I can't modify. Based on OS
posts I've tried different things. The ones that I think brought me close are:
I created a .values() queryset that looks like this
def my_queryset():
results=TodaysResults.objects.values('id','foreignModel1__name',
'foreignModel1__ForeignModel2__title')
print (myquery_set)
my_queryset gives me all the values I need. So I tried to convert it using the methods below.
1)
def make_querydict():
results=TodaysResults.objects.values('id','foreignModel1__name',
'foreignModel1__ForeignModel2__title')
json_results=json.dumps(list(results,cls=DjangoJSONEncoder))
print (json_results)
I get the following error:
"TypeError: list() takes no keyword arguments"
I tried this as well:
def serialize():
fields = ['id','foreignModel1__name','foreignModel1__ForeignModel2__title']
qs = TodaysResults.objects.all()
json_data = serializers.serialize('json',qs,fields=fields)
print(json_data)
But when I print the json_data in only shows id and not the foreign values.
Based on a few answers like this(from 2012) that were along the same lines And I tried:
def for_JSON_response():
response=JsonResponse(dict(results_info=list(TodaysResultslts.objects.values('id','foreignModel1__name',
'foreignModel1__ForeignModel2__title')
print(response)
I don't get any errors but it does not print anything.So, I'm assuming nothing happened.
Based on this I tried:
def my_queryset():
results=TodaysResults.objects.values('id','foreignModel1__name',
'foreignModel1__ForeignModel2__title')
print (JsonResponse(results, safe=False))
I get:
TypeError: Object of type QuerySet is not JSON serializable
And I tried:
def my_queryset():
results=TodaysResults.objects.values('id','foreignModel1__name',
'foreignModel1__ForeignModel2__title')
results_json = serializers.serialize('json',results)
And I got:
AttributeError: 'dict' object has no attribute '_meta'
I've been looking around a lot and some of the responses look outdated. My attempts above are the ones that I believe came the closest to converting the
valuesqueryset to json or getting the values I need into JSON. Is there a way of making a chained query like the one I have in my_queryset and convert it to JSON?
Models.Py
Simplified for this example
class TodaysResults(models.Model):
place = models.CharField(max_length=255)
ForeignModel1 = models.ForeignKey(ForeignModel,related_name='m1')
class ForeignModel(models.Model):
name = Models.CharField(max_length=255)
ForeignModel2 = models.ManyToManyField(M2M, related_name='m2')
class M2M(models.Model):
title = Models.CharField(max_length=255)
Here we have three model TodaysResults, ForeignModel and MModel and I am guessing MModel is a manyTomany relationship with ForeignModel. I am proposing two possible way how we can get all information from TodaysResults serializer.
Possible Solution One
from rest_framework import serializers
class MModelSerializer(serializers.ModelSerializer):
class Meta:
model = MModel
fields = ('title',)
class ForeignModelSerializer(serializers.ModelSerializer):
foreignModel2 = MModelSerializer(many=True) # as it is many to many field
class Meta:
model = ForeignModel
fields = ('name', 'foreignModel2',)
class TodaysResultsSerializer(serializers.ModelSerializer):
foreignModel1 = ForeignModelSerializer()
class Meta:
model = TodaysResults
fields = ('place', 'foreignModel1')
Now pass your TodaysResults queryset TodaysResultsSerializer and from serializer.data you will get your serialized data.
Possible Solution Two
Even we can do this with one serialize as there is not all fields is required.
class TodaysResultsSerializer(serializers.ModelSerializer):
class Meta:
model = TodaysResults
fields = ('place', 'foreignModel1__name', 'foreignModel1__foreignModel2__title')
Though i am not fully sure but this should also work.

Django Rest Framework: How to pass a list of UUIDs for a nested relationship to a serializer?

TL;DR: What could be the reason the incoming data for one of my serializers does not get processed?
I'm working on a serializer for a nested relationship. The serializer should get a list of UUIDs, so that I can make many to many relationships. Here is the model:
class Order(
UniversallyUniqueIdentifiable,
SoftDeletableModel,
TimeStampedModel,
models.Model
):
menu_item = models.ForeignKey(MenuItem, on_delete=models.CASCADE)
custom_choice_items = models.ManyToManyField(CustomChoiceItem, blank=True)
price = models.ForeignKey(MenuItemPrice, on_delete=models.CASCADE)
amount = models.PositiveSmallIntegerField(
validators=[MinValueValidator(MINIMUM_ORDER_AMOUNT)]
)
Here is the data (my post body) with which I hit the route in my tests:
data = {
"checkin_uuid": self.checkin.uuid,
"custom_choice_items": [],
"menu_item": self.menu_item.uuid,
"price": self.menu_item_price.uuid,
"amount": ORDER_AMOUNT,
}
response = self.client.post(self.order_consumer_create_url, self.data)
Note that the empty list for custom_choice_items does not change anything. Even if I fill it with values the same error occurs. And last but not least here are the serializers:
class CustomChoiceItemUUIDSerializer(serializers.ModelSerializer):
"""Serializer just for the uuids, which is used when creating orders."""
class Meta:
model = CustomChoiceItem
fields = ["uuid"]
....
# The serializer that does not work
class OrderSerializer(serializers.ModelSerializer):
menu_item = serializers.UUIDField(source="menu_item.uuid")
custom_choice_items = CustomChoiceItemUUIDSerializer()
price = serializers.UUIDField(source="price.uuid")
wish = serializers.CharField(required=False)
class Meta:
model = Order
fields = [
"uuid",
"menu_item",
"custom_choice_items",
"price",
"amount",
"wish",
]
The problem is now, that when I leave out many=True, I get the error:
{'custom_choice_items': [ErrorDetail(string='This field is required.', code='required')]}
And If I set many=True I just simply don't get any data. By that I mean e.g. the value of validated_data["custom_choice_items"] in the serializers create() method is just empty.
What goes wrong here?
I even checked that the data is in the request self.context["request"].data includes a key custom_choice_items the way I pass the data to this view!
EDIT: Here is the data I pass to custom_choice_items:
data = {
“checkin_uuid”: self.checkin.uuid,
“custom_choice_items”: [{“uuid”: custom_choice_item.uuid}],
“menu_item”: self.menu_item.uuid,
“price”: self.menu_item_price.uuid,
“amount”: ORDER_AMOUNT,
}
self.client.credentials(HTTP_AUTHORIZATION=“Token ” + self.token.key)
response = self.client.post(self.order_consumer_create_url, data)
When you post data using the test api client, if the data contains nested structure you should use format=json, like this:
response = self.client.post(self.order_consumer_create_url, data, format='json')
Did you override .create method in the serializer? Something like this should work:
from django.db import transaction
class OrderSerializer(serializers.ModelSerializer):
# your fields and Meta class here
#transaction.atomic
def create(self, validated_data):
custom_choice_items = validated_data.pop('custom_choice_items')
order = super().create(validated_data)
order.custom_choice_items.add(*custom_choice_items)
return order
By the way you don't really need to define CustomChoiceItemUUIDSerializer if is just the primary key of that.

Denormalizing models in tastypie

What i'm trying to do is to add a query result from a model to a modelresource, as you can see in this block of code:
def dehydrate(self, bundle):
bundle.data['image'] = place_image.image.get(place=1).get(cardinality=0)
I want to add a field to PlaceResource that will contain the image from place_site model where place=1 and cardinality=0. But im recieving an error:
The 'image' attribute can only be accessed from place_image instances
So, my question is: Is it impossible to use the query result from another model in a tastypie modelresource? Im sorry for my bad english, please correct me if something's wrong. Thanks for your time.
There's the complete code:
MODELS.py:
class place(models.Model):
idPlace = models.AutoField(primary_key=True)
Name = models.CharField(max_length=70)
class place_image(models.Model):
idImage = models.AutoField(primary_key=True)
place = models.ForeignKey(place,
to_field='idPlace')
image = ThumbnailerImageField(upload_to="place_images/", blank=True)
cardinality = models.IntegerField()
API.py
from models import place
from models import place_image
class PlaceResource(ModelResource):
class Meta:
queryset = place.objects.all()
resource_name = 'place'
filtering = {"name": ALL}
allowed_methods = ['get']
def dehydrate(self, bundle):
bundle.data['image'] = place_image.image.get(place=1).get(cardinality=0)
return bundle
class PlaceImageResource(ModelResource):
place = fields.ForeignKey(PlaceResource, 'place')
class Meta:
queryset = place_image.objects.all()
resource_name = 'placeimage'
filtering = {"place": ALL_WITH_RELATIONS}
allowed_methods = ['get']
The error you are getting is caused by the fact that you are accessing the image attribute of a model class, not instance.
The object that is being dehydrated in the dehydrate method is stored in obj attribute of the bundle parameter. Also, you are trying to filter place_image models to only those with place=1 and cardinality=0 by accessing the image attribute of place_image model class. Such filtering won't work as image is not a ModelManager instance. You should use objects attribute instead. Furthermore, get() method returns an actual model instance thus a subsequent call to get() will raise AtributeError as your place_image model instances have no attribute get.
So, all in all, your dehydrate should look like this:
def dehydrate(self, bundle):
bundle.data['image'] = place_image.objects.get(place_id=1, cardinality=0).image
return bundle
Notice that this code requires the place_image with desired values to exist, otherwise a place_image.DoesNotExist will be thrown.
There is also some redundancy in your models:
idPlace and idImage can be removed, as django by default creates an AutoField that is a primary key called id when no other primary key fields are defined
place_image.place field has a redundant to_field parameter, as by default ForeignKey points to a primary key field

Can a model manager access its models' Meta attribute (`Meta.unique_together`)?

Here's my attempt at a generalized natural key model manager. It's like the docs except it tries (unsuccessfully) to determine the natural key field names from the Meta.unique_together attribute.
class NaturalKeyModelManager(Manager):
def get_by_natural_key(self, *args):
field_dict = {}
for i, k in enumerate(self.model.Meta.unique_together[0]):
field_dict[k] = args[i]
return self.get(**field_dict)
If I insert a debug print just before the for loop like this:
print dir(self.model.Meta)
it doesn't list the unqiue_together attribute at all:
['__doc__', '__module__', 'abstract']
The 'abstract' bit worried me, but another debug print shows that the model I'm trying manage with natural keys is not abstract:
>>> print self.model.Meta.abstract
False
I am mixing in a lot of abstract base classes. Could that be the problem?
class MixedModel(NamedModel, TimeStampedModel, VersionedModel, Model):
objects = NaturalKeyModelManager()
class Meta:
unique_together = (('name', 'version',),)
For completeness here's one of the mixins:
class TimeStampedModel(Model):
created = DateTimeField(_("Created"), auto_now_add=True, null=True, editable=False)
updated = DateTimeField(_("Updated"), auto_now=True, null=True, editable=True)
class Meta:
abstract = True
The hard-coded model manager works just fine:
class MixedModelManager(Manager):
def get_by_natural_key(self, name, version):
return self.get(name=name, version=version)
In order to get the actual options passed to meta, you should use self.model._meta rather than self.model.Meta