Access belongs_to on a Mongoid::Document subclass - mongoid

I have a model 'Index' as:
class Index
include Mongoid::Document
belongs_to :project
end
Another model PercentileRankIndex inherits Index
class PercentileRankIndex < Index
def self.model_name
Index.model_name
end
end
Suppose I do :
p = Index.first (OR EVEN) p = PercentileRankIndex.first
I get this :
#<PercentileRankIndex _id: 51630ece34b2613d27000011, project_id: "51630ece34b2613d27000010", enabled: true, _type: "PercentileRankIndex", :enabled: "true">
However on doing
p.project
=>nil
The belongs_to relationship isnt working on child class. Why? How can I fix it?

Related

Removing Null nested fields in Django Serializer --- or how to iterate with Null?

This issue I am having is likely a React iteration problem but I think taking care of it at the root inside Django will be more efficient instead of on the front end.
Possible remedies:
ternary operation to conditionally display objects and parameters if not null
remove null fields from response to avoid needing to do ternary operation through django itself
Although, in the first place, I cannot seem to be able to iterate through my response.
I have a double nested serializer:
Models:
class Profile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
class VideoProduct(models.Model):
...
class ImageProduct(models.Model):
...
class ProfileProduct(models.Model):
profile = models.ForeignKey(Profile, on_delete=models.CASCADE)
video_product = models.ForeignKey(VideoProduct, on_delete=models.CASCADE, blank=True)
image_product = models.ForeignKey(VideoProduct, on_delete=models.CASCADE, blank=True)
class Meta:
unique_together = ('profile', 'video_product')
unique_together = ('profile', 'image_product')
Views.py:
class VideoProductViewSet(viewsets.ModelViewSet):
queryset = VideoProduct.objects.all()
serializer_class = VideoProductSerializer
class ImageProductViewSet(viewsets.ModelViewSet):
queryset = ImageProduct.objects.all()
serializer_class = VideoProductSerializer
class ProfileProductsViewSet(viewsets.ModelViewSet):
queryset = ProfileProduct.objects.all()
serializer_class = ProfileProductsSerializer
class ProfileBySlug(generics.ListAPIView):
serializer_class = ProfileBySlugSerializer
def get_queryset(self):
slug = self.request.query_params.get('slug', None)
queryset = Profile.objects.filter(slug=slug)
if slug is not None:
queryset = queryset.filter(slug=slug)
return queryset
Serializers:
class VideoProductSerializer(serializers.ModelSerializer):
class Meta:
model = VideoProduct
fields = ['id', 'price']
class ImageProductSerializer(serializers.ModelSerializer):
class Meta:
model = ImageProduct
fields = ['id', 'price']
class ProfileProductsSerializer(serializers.ModelSerializer):
video_product = VideoProductSerializer(read_only=True)
image_product = ImageProductSerializer(read_only=True)
class Meta:
model = ProfileProduct
fields = ['id', 'video_product', 'image_product']
class ProfileBySlugSerializer(serializers.ModelSerializer):
profile_products_set = serializers.SerializerMethodField('get_profile_products')
def get_profile_products(self, profile):
qs = ProfileProduct.objects.filter(profile=profile)
serializer = ProfileProductsSerializer(instance=qs, many=True)
return serializer.data
class Meta:
model = Profile
fields = ['id', 'title', 'description', 'slug', 'image', 'status', 'profile_products_set']
The issue is that a ProfileProduct will only have one association to either a video_product or image_product but my responses are like this:
API Request:
axios.get(`http://127.0.0.1:8000/api/v1/profile/`, {
params: {
slug: this.state.slug,
}
})
.then(res => {
this.setState({ profile: res.data.results[0] });
this.setState({ products: res.data.results[0].profile_products_set });
....
Entire response:
[{…}]
0:
description: "Description"
id: 1
profile_products_set: Array(2)
0:
video_product: {id: 1, price: "0.00", status: true}
image_product: null
__proto__: Object
1: {image_product: {…}, video_product: null}
length: 3
__proto__: Array(0)
title: "Admin Title"
__proto__: Object
length: 1
__proto__: Array(0)
I set the state for profile_products_set with:this.setState({ products: res.data.results[0].profile_products_set })
Now when I go to iterate within React:
{ this.state.products.map(product =>
<div>
<li>{product.id}</li>
<li>{product.price}</li>
</div>
)}
It is blank with only bullet points.
As mentioned above, how can I one iterate over this and display it on the front end, and then if possible, remove the null fields from the response all together to avoid needing to do and ternary operations based on the product type
You can use to_representation to override the serialization. You can then remove the entries with None value.
class ProfileProductsSerializer(serializers.ModelSerializer):
video_product = VideoProductSerializer(read_only=True)
image_product = ImageProductSerializer(read_only=True)
class Meta:
model = ProfileProduct
fields = ['id', 'video_product', 'image_product']
def to_representation(self, instance):
data = super().to_representation()
return {k: v for k, v in data.items() if v is not None}

How to count deep relationship fields with Django?

How could I count the relationship of the models below?
class Client(models.Model):
name = models.CharField(max_length=150)
class Pet(models.Model):
client = models.ForeignKey(Client, related_name='pet')
name = models.CharField(max_length=150)
class Photo(models.Model):
pet = models.ForeignKey(Pet, related_name='photo')
photo = models.ImageField(storage=DIR)
I would like to return the total pet and total photos taken of all pets dr to a single customer client, with the following answer:
[
{
"name": "john",
"totalPet": 3, // imagine 10 photos were taken of each pet
"totalPhoto": 30,
},
{
"name": "Mary",
"totalPet": 2, // imagine that 10 photos were taken of one and 5
// of another pet
"totalPhoto": 15,
},
]
My serializer is as follows:
class ClientSerializer(ModelSerializer):
totalPet = serializers.SerializerMethodField()
def get_totalPet(self, obj):
return obj.pet.count()
class Meta:
model = Client
fields = ('name', 'totalPet')
As can be observed, I can total the amount of pets for each customer, but I can't find a way to total the photos of all pets of a customer.
What would be the best way to do this photo count?
You can perform a filter on Photo model with your client.pet queryset, using the __in lookup.
At first, I would define a custom QuerySet for our Photo model:
from django.db import models
class PhotoQuerySet(models.QuerySet):
def for_client(self, client):
# Get all related pets to the current client
client_pets = client.pet.all()
# Filter Photos that are linked with these pets
return self.filter(pet__in=client_pets)
And then inside the model:
class Photo(models.Model):
...
objects = PhotoQuerySet.as_manager()
This will allow you to do the following: Photo.objects.for_client(client=...)
After that, you can use this method in the serializer:
class ClientSerializer(ModelSerializer):
total_pets = serializers.SerializerMethodField()
total_photos = serializers.SerializerMethodField()
def get_total_pets(self, obj):
return obj.pet.count()
def get_total_photos(self, obj):
# Calling our newly created method and aggregating count
return Photo.objects.for_client(client=obj).count()
class Meta:
model = Client
fields = ('name', 'total_pets', 'total_photos')

Mongoid override relation setter

I'm trying to implement this pattern
class A
Mongoid::Document
belongs_to :price
def price
self[:price] || calculate_price
end
def calculate_price
#Some logic
end
end
meaning that a user can either force a price to A or get a calculated price. Trouble is, the setter doesn't work as expected:
2.0.0-rc2 :013 > a = A.new
=> #<A _id: 5215b3321d41c89a1f000001, price_id: nil>
2.0.0-rc2 :015 > a.price = Price.new
=> #<Price _id: 5215b3451d41c89a1f000002, i(inclusive_tax): nil, e(exclusive_tax): nil, tax_id: nil>
2.0.0-rc2 :016 > a.price
=> "5215b3451d41c89a1f000002"
What is the way to override the setter so things work as expected?
I tried to add a
def price=(val)
super(val)
end
but there is no super for the setter.
Any hint?
What about this work around?
class A
Mongoid::Document
belongs_to :price
def price
Price.find(self[:price]) || calculate_price
end
def calculate_price
#Some logic
end
end

mongoid polymorphic association error

I'm having some problems using mongoid-3.0.6 with polymorphic fields.
Using rails 3.2.8 and ruby 1.9.3
Using a normal polymorphic relation:
class Shirt
include Mongoid::Document
field :name, localize: true
belongs_to :itemizable, polymorphic: true
end
class Item
include Mongoid::Document
field :price, type: Float
field :quantity, type: Integer, :default => 1
has_one :product, as: :itemizable
accepts_nested_attributes_for :product
end
The same association is available through the metadata:
>> Item.reflect_on_association(:product)
#<Mongoid::Relations::Metadata
  autobuild: false,
  class_name: Product,
  cyclic: nil,
  dependent: nil,
  inverse_of: nil,
  key: _id,
  macro: has_one,
  name: product,
  order: nil,
  polymorphic: true,
  relation: Mongoid::Relations::Referenced::One,
  setter: product=,
  versioned: false>
>> item = Item.new
#<Item _id: 50606c1668ce87692e000003, _type: nil, created_at: nil, updated_at: nil, deleted_at: nil, price: nil, quantity: 1>
but when i run
>> item.product = Shirt.new or >> item.build_product
i got always the same error
NameError: uninitialized constant Product
Full stack error
Any thoughts?
Thanks in advance.
Solved
Found the motive
Need to add the class_name to the relation
has_one :product, as: :itemizable, class_name: "Toy"

Mongoid relation not being persisted

class Account
include Mongoid::Document
include Geocoder::Model::Mongoid
geocoded_by :zip
devise :database_authenticatable, :registerable, :recoverable, :rememberable, :trackable, :validatable
before_validation :pass_confirm
after_validation :geocode_check
before_create :assign_subs
field :email, type: String
field :type, type: String
field :zip, type: String
field :oldzip, type: String
field :coordinates, type: Array
field :latitude, type: Float
field :longitude, type: Float
auto_increment :num, collection: :account_nums
index :num, unique: true
has_many :submissions
mount_uploader :photo, PhotoUploader
def self.find_by_num(num)
Account.where(num: num).first
end
protected
def pass_confirm
self.password_confirmation ||= self.password
end
def geocode_check
if self.oldzip != self.zip
self.oldzip = self.zip
self.geocode
end
end
def assign_subs
binding.pry
Submission.where(email: self.email).each do |sub|
sub.zip = self.zip
self.submissions << sub
end
end
end
--
class Submission
include Mongoid::Document
include Mongoid::Search
include Mongoid::Timestamps::Created
include Geocoder::Model::Mongoid
geocoded_by :zip
before_validation :fix_rate
after_validation :geocode
search_in :message, tags: :name
field :email, type: String
field :rate, type: String
field :message, type: String
field :type, type: String
field :zip, type: String
field :coordinates, type: Array
field :latitude, type: Float
field :longitude, type: Float
auto_increment :num, collection: :submission_nums
index :num, unique: true
has_and_belongs_to_many :tags
belongs_to :account
mount_uploader :photo, PhotoUploader
protected
def fix_rate
self.rate = self.rate.sub(/[^\d]*/, '').sub(/(\d*).*/, '\1')
end
end
--
pry(#<Account>)> self.submissions << Submission.first
=> [#<Submission _id: 4e751df86066252059000054, _type: nil, created_at: 2011-09-17 22:23:52 UTC, _keywords: ["tfnwuaty"], email: "krisbltn#gmail.com", rate: "49", message: "tfnwuaty", type: "person", zip: nil, coordinates: nil, latitude: nil, longitude: nil, num: 1, tag_ids: [], account_id: BSON::ObjectId('4e751e0d6066252059000059'), photo: "lawrence.jpg">]
pry(#<Account>)> self.submissions
=> []
as you see above, when trying to add a child document it doesn't get saved. Any ideas as to what could be going on?
Also- this is a has_many / belongs_to relationship, and when I change it to has_and_belongs_to_many it seems to work fine.
My guess is that you've upgraded Mongoid, but haven't read the upgrading docs.
Add , :autosave => true to Account's relation with Submission.
class Account
include Mongoid::Document
has_many :submissions, :autosave => true
end
class Submission
include Mongoid::Document
belongs_to :account
end
Account.delete_all
Submission.delete_all
Submission.create
account = Account.new
account.submissions << Submission.first
account.save
Submission.first.account == account
This was also submitted as a GitHub issue. Tsk tsk.

Resources