I have three models in my Django application - simplified for demonstration. The first one is Base. The second one is Bill and has foreign key "base" to Base model. The third one is Point, and has a foreign key to Bill.
class Base(models.Model):
type = models.CharField()
desc = models.IntegerField()
class Bill(models.Model):
base = models.ForeignKey("Base", related_name="bills")
total_value = models.DecimalField()
class Point(models.Model):
bill = models.ForeignKey("Bill", related_name="points")
value = models.DecimalField()
My goal is to have a property for Base called def bill(self), that would sum up all "bill" objects fields and returned a bill-like instance. I would like to get a base object:
my_base = Base.objects.get()
bill = Base.bill <-- I can figure this out with #property
points = bill.points <-- this does not work through #property function.
My current solution is like this, to get the first part working:
class Base():
...
#property
def bill(self):
sums = self.bills.aggregate(total_value=Sum('total_value'))
return Bill(**sums)
The second part, that would sum up bill-related-points and return them in a my_base.bill.points, does not work. If I filter for points and try to assign them to Bill(**sums).points = filtered_points, I get an error: Direct assignment to the reverse side is prohibited, or Unsaved model instance cannot be used in an ORM query
Is there a more elegant solution to this? A good option would be to initate class as a #property like so:
class Base(model.Model):
...
class bill(...):
self.total_value = .
self.points = .
but I don't believe that is achievable.
Thank you
Related
I'm using the pythons factory_boy package to create instances of models for testing purposes.
I want to pass the parameters used when calling Facotry.create() to all the SubFactories in the Factory being called.
Here's how I do it now:
Example 1:
I have to explicitly set the company when calling the SubFactory (the BagFactory)
class BagTrackerFactory(BaseFactory):
company = factory.SubFactory(CompanyFactory)
bag = factory.SubFactory(BagFactory, company=factory.SelfAttribute("..company"))
class BagFactory(BaseFactory):
company = factory.SubFactory(CompanyFactory)
Example 2:
In this example, I have to add company to Params in the BagFactory, so I can pass it down to ItemFactory which has the company parameter.
class BagTrackerFactory(BaseFactory):
company = factory.SubFactory(CompanyFactory)
bag = factory.SubFactory(BagFactory, company=factory.SelfAttribute("..company"))
class BagFactory(BaseFactory):
item = factory.SubFactory(ItemFactory, company=factory.SelfAttribute("..company"))
class Params:
company = factory.SubFactory(CompanyFactory)
class ItemFactory(BaseFactory):
company = factory.SubFactory(CompanyFactory)
The reason why I do it like this is that it saves time and it makes sense that the Bag belongs to the same company as the BagTracker when created by the same Factory.
Note: the BaseFactory is factory.alchemy.SQLAlchemyModelFactory
Question:
What I would like is to have company (and all the other parameters) from the parent Factory be passed down to SubFactories without having to pass it explicitly. And this continues downstream all the way to the last SubFactory, so every model has the same company, from the topmost parent Factory to the lowest child SubFactory. I hope you understand what I'm saying.
Is there an easy way to do this? Like some option in the factory-boy package?
EDIT:
I ended up doing it the long way, passing down parameters manually. In this example, I'm showing both cases: when the parent factory has the company parameter(BagTrackerFactory) and doesn't have it but must pass it downstream (BagFactory).
class CompanyFactory(BaseFactory):
id = get_sequence()
class Meta:
model = Company
class ItemFactory(BaseFactory):
id = get_sequence()
owner = factory.SubFactory(CompanyFactory)
owner_id = factory.SelfAttribute("owner.id")
class Meta:
model = Item
class BagFactory(BaseFactory):
id = get_sequence()
item = factory.SubFactory(ItemFactory, owner=factory.SelfAttribute("..company"))
item_id = factory.SelfAttribute("item.id")
class Params:
company = factory.SubFactory(CompanyFactory)
class Meta:
model = Bag
class BagTrackerFactory(BaseFactory):
id = get_sequence()
company = factory.SubFactory(CompanyFactory)
company_id = factory.SelfAttribute("company.id")
item = factory.SubFactory(ItemFactory, owner=factory.SelfAttribute("..company"))
item_id = factory.SelfAttribute("item.id")
bag = factory.SubFactory(BagFactory, company=factory.SelfAttribute("..company"))
bag_id = factory.SelfAttribute("bag.id")
class Meta:
model = BagTracker
This is possible, but will have to be done specifically for your codebase.
At its core, a factory has no knowledge of your models' specific structure, hence can't forward the company field — for instance, some models might not accept that field in their __init__, and providing the field would crash.
However, if you've got a chain where the field is always accepted, you may use the following pattern:
class WithCompanyFactory(factory.BaseFactory):
class Meta:
abstract = True
company = factory.Maybe(
"factory_parent", # Is there a parent factory?
yes_declaration=factory.SelfAttribute("..company"),
no_declaration=factory.SubFactory(CompanyFactory),
)
This works thanks to the factory_parent attribute of the stub used when building a factory's parameters: https://factoryboy.readthedocs.io/en/stable/reference.html#parents
This field either points to the parent (when the current factory is called as a SubFactory), or to None. With a factory.Maybe, we can copy the value through a factory.SelfAttribue when a parent is defined, and instantiate a new value.
This can be used afterwards in your code:
class ItemFactory(WithCompanyFactory):
pass
class BagFactory(WithCompanyFactory):
item = factory.SubFactory(ItemFactory)
class BagTrackerFactory(WithCompanyFactory):
bag = factory.SubFactory(BagFactory)
>>> tracker = BagTrackerFactory()
>>> assert tracker.company == tracker.bag.company == tracker.bag.item.company
... True
# It also works starting anywhere in the chain:
>>> company = Company(...)
>>> bag = BagFactory(company=company)
>>> assert bag.company == bag.item.company == company
... True
If some models must pass the company value to their subfactories, but without a company field themselves, you may also split the special WithCompanyFactory into two classes: WithCompanyFieldFactory and CompanyPassThroughFactory:
class WithCompanyFieldFactory(factory.Factory):
"""Automatically fill this model's `company` from the parent factory."""
class Meta:
abstract = True
company = factory.Maybe(
"factory_parent", # Is there a parent factory?
yes_declaration=factory.SelfAttribute("..company"),
no_declaration=factory.SubFactory(CompanyFactory),
)
class CompanyPassThroughFactory(factory.Factory):
"""Expose the parent model's `company` field to subfactories declared in this factory."""
class Meta:
abstract = True
class Params:
company = factory.Maybe(
"factory_parent", # Is there a parent factory?
yes_declaration=factory.SelfAttribute("..company"),
no_declaration=factory.SubFactory(CompanyFactory),
)
I have some code in which I have defined some Python models.
For example:
class Store(object):
'''
The store object... Should it be a Django model?
'''
def __init__(self, *args, **kwargs):
self.name = kwargs['name']
self.id = kwargs['id']
def __str__(self, *args, **kwargs):
return self.name
kittenstore = Store(name='kittenstore', id=1)
dogstore = Store(name='dogstore', id=2)
stores_list = [kittenstore, dogstore]
class Stores(object):
def __init__(self, *args, **kwargs):
self.stores = stores_list
def get_choices(self):
store_choices = ()
for store in self.stores:
store_choices+= ((store.id, store.name),)
return store_choices
I'd like to refer to those stores from another Django model.
I could use a models.Integerfield with choices=get_choices(), but when I filter in a related models, then I always get the id back and I need to find the correlating store if I want to use it in my code.
Is there not a (python) trick to get back the Store object instead of the id?
Maybe with a custom Field?
"Referring to these stores from another model" implies that there should be some kind of (logical) relationship between those models. This is usually depicted by connecting the models with a foreign key.
In other words, you can try something like this:
from django.db import models
class Store(models.Model):
name = models.CharField(max_length=50)
id = models.CharField(max_length=5)
def __str__(self):
return 'Store is named: %s' % self.name
class Stores(models.Model):
store = models.ForeignKey(Store, on_delete=models.CASCADE)
def __str__(self):
return "Stores is at %s store" % (self.store)
Create your store and save it:
kittenstore = Store(name='kittenstore', id='1')
kittenstore.save()
Instantiate your Stores:
st = Stores(id=None, store=kittenstore)
st.save()
And now you can access your store object like this:
st.kittenstore
The relationship I used in this example is a Many-to-one relationship. You should decide on the relationship that best suits your needs by taking a look at the Django documentation regarding model relationship.
***EDIT***If you mean associate them via the model's attributes then you can assign a model attribute to an instance of a class. For a related example on the matter, take a look at this.
This question expands on this one.
Suppose my models are
class Car(models.Model):
images = models.ManyToManyField(Image)
class Image(models.Model):
path = models.CharField()
type = models.CharField()
and my filter class is
class CarFilter(django_filters.FilterSet):
having_image = django_filters.Filter(name="images", lookup_type='in')
class Meta:
model = Car
then I can make GET queries like ?having_image=5 and get all cars that have an image with pk=5. That's fine. But what if I need to return both cars with this image AND also cars with no images at all, in one list? How do I unite two conditions in one django_filters.Filter?
I don't think that's supported out of the box with django-filter. You could try to create your own Filter class and override the filter() method.
Querysets do support the | (union) operator, so you can combine the default result with the results where the field result is None.
class AndNoneFilter(django_filters.Filter):
def filter(self, qs, value):
return qs(**{self.field_name: None}) | super().filter(qs, value)
class CarFilter(django_filters.FilterSet):
having_image = AndNoneFilter(field_name='images', lookup_type='in')
I've not tested this code, so it might not work exactly as written, but you get the idea.
I am trying to show a M2M field in a django-table2 as seen in Django-tables2: How to use accessor to bring in foreign columns? and Accessing related models with django-tables2
Using: foreigncolumn = tables.Column(accessor='foreignmodel.foreigncolumnname'), I only see a '--'...
# The models:
class Organism(models.Model):
species_name = models.CharField(max_length=200)
strain_name = models.CharField(max_length=200)
eukaryotic = models.BooleanField(default=True)
lipids = models.ManyToManyField('Lipid',blank=True)
class Lipid(models.Model):
lm_id = models.CharField(max_length=100)
common_name = models.CharField(max_length=100,blank=True)
category = models.CharField(max_length=100,blank=True)
#The tables
class OrganismTable(tables.Table):
name = tables.LinkColumn('catalog:organism-detail', text=lambda record: record.species_name, args=[A('pk')])
lp = tables.Column(accessor='Lipid.common_name')
class Meta:
model = Organism
sequence = ['name','lp']
exclude = ['id','species_name']
Any idea what I'm doing wrong?
This does not work so easily for ManyToManyFields because of the simple way Accessor works. You could display the repr of the related QuerySet via 'lipids.all' but that does not seem sufficient here. You can, however, add a property (or method) to your Organism model and use it in the accessor. This way, you can display any custom information related to the instance:
class Organism(models.Model):
# ...
#property
def lipid_names(self):
return ', '.join(l.common_name for l in self.lipids.all()) # or similar
class OrganismTable(tables.Table):
# ...
lp = tables.Column(accessor='lipid_names')
I would recommend then to add a prefetch_related('lipids') to the Organism QuerySet that you pass to the table for better performance.
I'm following the method used by #Yauhen Yakimovich in this question:
do properties work on django model fields?
To have a model field that is a calculation of a different model.
The Problem:
FieldError: Cannot resolve keyword 'rating' into field. Choices are: _rating
The rating model field inst correctly hidden and overridden by my rating property causing an error when I try to access it.
My model:
class Restaurant(models.Model):
...
...
#property
def rating(self):
from django.db.models import Avg
return Review.objects.filter(restaurant=self.id).aggregate(Avg('rating'))['rating__avg']
Model in Yauhen's answer:
class MyModel(models.Model):
__foo = models.CharField(max_length = 20, db_column='foo')
bar = models.CharField(max_length = 20)
#property
def foo(self):
if self.bar:
return self.bar
else:
return self.__foo
#foo.setter
def foo(self, value):
self.__foo = value
Any ideas on how to correctly hid the rating field and define the #property technique?
Solved by using sorted()
I was using a query with order_by() to call rating. order_by() is at the database level and doesnt know about my property. Soultion, use Python to sort instead:
sorted(Restaurant.objects.filter(category=category[0]), key=lambda x: x.rating, reverse=True)[:5]
If you encounter a similar error check through your views for anything that might be calling the property. Properties will no longer work at the datatbase level.
Change this line:
self._rating = Review.objects.filter(restaurant=self.id).aggregate(Avg('rating'))['rating__avg']
into this one:
self._rating = Review.objects.filter(restaurant=self.id).aggregate(Avg('_rating'))['_rating__avg']
(notice change of reference in query from rating and rating__avg to _rating and _rating__avg)