As new to both Python and Django I have encounterd what I think is a simple problem, but which does not want to go away. I have a "table" with factors obtained from
class TjgFaktor(models.Model):
typ = models.CharField(max_length=2)
factor = models.FloatField()
Next I have another class which is foreign-keyed to this:
class Moment(models.Model):
typ = models.ForeignKey(TjgFaktor,on_delete=models.SET_NULL,null=True)
Now, what I want to do is to get the factor from the first class as an attribut to Moment. I have tried
def factor(self):
return TjgFaktor.objects.get(typ=self).factor
in the hope of getting the correct factor. However, when I do something like
person_moment = Moment.objects.all()
for e in person_moment:
print(e.factor())
what I get is "TjgFaktor matching query does not exist".
So how should I do this? I guess it is the function: it works if I replace type=self with pk=1.
You do not need to obtain the TjgFaktor through an explicit query. If you query for some_moment.typ, Django itself will perform an implcit query to fetch the TjgFaktor that corresponds to the Moment (through the foreign key), or None, if the foreign key is set to None.
We can thus query like:
def factor(self):
tjgfaktor = self.typ
if tjgfaktor:
return tjgfaktor.factor
In case there is no related TjgFaktor, then this function will return None as well.
In case you define a large amount of values, then this fetch might be inefficient: Django will fetch all columns from the database, and since we are only interested in a single one, this will thus result in some overhead.
We can avoid that by using the following query:
def factor(self):
if self.typ_id:
return (TjgFaktor.objects.values_list('factor', flat=True)
.get(pk=self.typ_id))
Assuming factor is function within Moment class, you can access factor if Moment object has related TjgFaktor object:
def factor(self):
return self.typ.factor if self.typ else None
So, the in the factor method, you need to enter the value for typ as a string value like this: A self does not satisfy the conditions of a string parameter that is required. You could do something like this -
def factor(self):
return TjgFaktor.objects.get(typ="YOUR_TYPE_IN_STRING").factor
def factor(self):
return TjgFaktor.objects.get(typ=self).factor
You can't actually compare the object of Moment with objects in TjgFaktor. You can directly access the values of parent model or foreignkey directly by doing like this.
e.typ.factor #this will directly give you factor values of foreign key.
Or you can compare with
def factor(self):
return TjgFaktor.objects.get(typ=self.typ.id).factor
Related
Is it possible to filter a Django queryset by model property?
i have a method in my model:
#property
def myproperty(self):
[..]
and now i want to filter by this property like:
MyModel.objects.filter(myproperty=[..])
is this somehow possible?
Nope. Django filters operate at the database level, generating SQL. To filter based on Python properties, you have to load the object into Python to evaluate the property--and at that point, you've already done all the work to load it.
I might be misunderstanding your original question, but there is a filter builtin in python.
filtered = filter(myproperty, MyModel.objects)
But it's better to use a list comprehension:
filtered = [x for x in MyModel.objects if x.myproperty()]
or even better, a generator expression:
filtered = (x for x in MyModel.objects if x.myproperty())
Riffing off #TheGrimmScientist's suggested workaround, you can make these "sql properties" by defining them on the Manager or the QuerySet, and reuse/chain/compose them:
With a Manager:
class CompanyManager(models.Manager):
def with_chairs_needed(self):
return self.annotate(chairs_needed=F('num_employees') - F('num_chairs'))
class Company(models.Model):
# ...
objects = CompanyManager()
Company.objects.with_chairs_needed().filter(chairs_needed__lt=4)
With a QuerySet:
class CompanyQuerySet(models.QuerySet):
def many_employees(self, n=50):
return self.filter(num_employees__gte=n)
def needs_fewer_chairs_than(self, n=5):
return self.with_chairs_needed().filter(chairs_needed__lt=n)
def with_chairs_needed(self):
return self.annotate(chairs_needed=F('num_employees') - F('num_chairs'))
class Company(models.Model):
# ...
objects = CompanyQuerySet.as_manager()
Company.objects.needs_fewer_chairs_than(4).many_employees()
See https://docs.djangoproject.com/en/1.9/topics/db/managers/ for more.
Note that I am going off the documentation and have not tested the above.
Looks like using F() with annotations will be my solution to this.
It's not going to filter by #property, since F talks to the databse before objects are brought into python. But still putting it here as an answer since my reason for wanting filter by property was really wanting to filter objects by the result of simple arithmetic on two different fields.
so, something along the lines of:
companies = Company.objects\
.annotate(chairs_needed=F('num_employees') - F('num_chairs'))\
.filter(chairs_needed__lt=4)
rather than defining the property to be:
#property
def chairs_needed(self):
return self.num_employees - self.num_chairs
then doing a list comprehension across all objects.
I had the same problem, and I developed this simple solution:
objects = [
my_object
for my_object in MyModel.objects.all()
if my_object.myProperty == [...]
]
This is not a performatic solution, it shouldn't be done in tables that contains a large amount of data. This is great for a simple solution or for a personal small project.
PLEASE someone correct me, but I guess I have found a solution, at least for my own case.
I want to work on all those elements whose properties are exactly equal to ... whatever.
But I have several models, and this routine should work for all models. And it does:
def selectByProperties(modelType, specify):
clause = "SELECT * from %s" % modelType._meta.db_table
if len(specify) > 0:
clause += " WHERE "
for field, eqvalue in specify.items():
clause += "%s = '%s' AND " % (field, eqvalue)
clause = clause [:-5] # remove last AND
print clause
return modelType.objects.raw(clause)
With this universal subroutine, I can select all those elements which exactly equal my dictionary of 'specify' (propertyname,propertyvalue) combinations.
The first parameter takes a (models.Model),
the second a dictionary like:
{"property1" : "77" , "property2" : "12"}
And it creates an SQL statement like
SELECT * from appname_modelname WHERE property1 = '77' AND property2 = '12'
and returns a QuerySet on those elements.
This is a test function:
from myApp.models import myModel
def testSelectByProperties ():
specify = {"property1" : "77" , "property2" : "12"}
subset = selectByProperties(myModel, specify)
nameField = "property0"
## checking if that is what I expected:
for i in subset:
print i.__dict__[nameField],
for j in specify.keys():
print i.__dict__[j],
print
And? What do you think?
i know it is an old question, but for the sake of those jumping here i think it is useful to read the question below and the relative answer:
How to customize admin filter in Django 1.4
It may also be possible to use queryset annotations that duplicate the property get/set-logic, as suggested e.g. by #rattray and #thegrimmscientist, in conjunction with the property. This could yield something that works both on the Python level and on the database level.
Not sure about the drawbacks, however: see this SO question for an example.
I have below model in my Django app.
class Revenue(models.Model):
from_a = models.IntegerField()
from_b = models.IntegerField()
def get_total(self):
return self.from_a + self.from_b
Now I am retrieving data using Revenue.objects.filter(from_a__gt = 10).values('from_a', 'from_b').
From the above queryset I am getting values, now I want to call get_total function on objects.
I didn't found a way to call that function.
Is there a way to retrieve the data only I needed using values and also can call member_functions of that objects?
Revenue.objects.filter(from_a__gt = 10) should not be the solution if I have 100s of columns for my model.
Instead of doing this on model level you can use F expression with your query:
from django.db.models import F
Revenue.objects.filter(from_a__gt = 10).annotate(
get_total=F('from_a') + F('from_b')
).values('from_a', 'from_b', 'get_total')
From the above queryset I am getting values, now I want to call get_total function on objects. I didn't found a way to call that function.
Well you obtain a QuerySet (that is at that point not evaluated), so a collection of Revenues. You can not directly call the function on that collection. But you can iterate through the queryset, and call the function on the inidvidual objects. We can for example make a list with:
[r.get_total() for r in Revenue.objects.filter(from_a__gt = 10)]
Is there a way to retrieve the data only I needed using values and also can call member_functions of that objects?
Yes, you can use the .only(..) element on the query, to restrict the number of columns that are loaded:
[r.get_total() for r in Revenue.objects.filter(from_a__gt = 10).only('from_a', 'from_b')]
This will construct Revenue objects, but we will only load the specified columns. In that case we will load only from_a and from_b, and if you later need other fields, these will be loaded with extra queries.
In case however the logic in the member_functions is easy, you better use annotations: these are then processed in the database, and thus allow filtering. This is however not always possible: Python allows to calculate very complicated things that would result in a gigantic equivalent SQL expression. Furthermore most databases do not allow to contact webservices and file systems, so some functions are fundamentally impossible to translate in an annotation.
Depends in what you want to do... if you want get_total shows as field, you can make this method one property method, he will be calculated field, so its not persist in data base
Property Method
#property
def get_total(self):
return self.from_a + self.from_b
Revenue.objects.filter(from_a__gt = 10).values('from_a', 'from_b', 'get_total')
https://docs.djangoproject.com/en/2.0/topics/db/models/#model-methods
You can call it like regular class method, so when you get the model instance you can call it
revenues = Revenue.objects.filter(from_a__gt = 10).values('from_a', 'from_b')
for revenue in revenues:
print(revenue.get_total)
# OR
[r.get_total for r in Revenue.objects.filter(from_a__gt = 10)] # Like Wilem Van said
Static Method
Using it as static method (this way isnt the best fit for your need but is nice to know), you will bind this method with the class, so you dont need one object instance to manage it, just the class reference
class Revenue(...):
...
#staticmethod
def get_total(valueA, valueB):
return valueA + valueB
To call it is like that
Revenue.get_total(5, 10)
https://www.programiz.com/python-programming/methods/built-in/staticmethod
Obs.: Weird, didnt got one good referente to staticmethod at django docs
is there any possibility to define a Field which gets incremented, but starts with a letter? So the values should be like: S123, S124, S125.
I' d like to use the id field for it, but some reason i' d like to have it as a CharField.
As a workaround i could use:
id = models.CharField(max_length = 32, primary_key = True)
and redefine the save method, so i always precalculate its value, but this won' t be that robust than a "real" solution, and also my solution would too slow with the calculation.
Is there a proper solution for my problem?
Django: 1.9.2
Python: 3.4.2
.
I don't think you should manually define a primary key. Django usually uses relational database to build an app, which means it would rely on some key field to join other tables to do the lookup when it needs to. Having primary keys like S123 makes it extra hard to maintain because you need to store the same thing as a reference in other tables.
What I would suggest is storing the letter part and the digits separately. You could use the default id field django created as the digit part and create your own field to store the letter part. Then you would use a property method to return the value you want to have. Roughly:
class Foo(models.Model):
letter = models.CharField(max_length=1)
#property
def symbol(self):
return '%s%s' % (self.letter, self.id)
Then you could do:
foo = Foo.objects.create(letter='S')
print foo.symbol # this would print S1, S2, etc.
In case you don't know, here's an explanation of #property in python.
I am writing a web based music application and I want implement some feature that user can see most favor album in last week-month-year.
so this is my model :
class album(models.Model):
def get_weely_count():
...
def get_monthly_count():
...
def get_yearly_count():
...
class like(models.Model):
created_at = models.DateField()
albumID = models.ForeignKey(Album)
Now I want to receive albums that most liked in last week or last month or last year,I want done some thing like this(but I can not):
Album.objects.all().order_by('get_weekly_count')
can any one help me to fix it or give another approach to achieve that goal??
The order_by method translates into an SQL ORDER BY, therefore it works only with model fields, which correspond to table columns. It won't work if you intend to sort your elements by a model's method.
So, if you want to accomplish something like
Album.objects.all().order_by('get_weekly_count')
You'll have to do it the python way
sorted(Album.objects.all(), key=lambda x: x.get_weekly_count())
Performance-wise, this means you'll get your elements with a query and then you'll sort them with python (that's different from getting a sorted queryset in one shot).
Otherwise, if it's possible for you to turn get_weekly_count into raw SQL, you could use it with a Count() or an extra modifier, that would make order_by usable, i.e.:
Album.objects.all().extra(
select={'weekly_count': "<some SQL>"},
select_params=(<you params>,),
).order_by('weekly_count')
Have a look at https://docs.djangoproject.com/en/1.8/ref/models/querysets/#extra
According to the documentation, you should use:
from django.db.models import Count
like.objects.filter(created_at__gt=START_OF_MONTH, created_at__lt=Datetime.now()).values('albumID').annotate(count=Count('albumID')).order_by('count')
This will get results for you in single db query. For more details visit https://docs.djangoproject.com/en/dev/topics/db/aggregation/.
I have this query that looks like this:
blah = Blah.objects.filter(foo=title)
It works fine, so I can use the results in a template. BUT, I need to change one of the values in it, from an integer to a timedelta so it will display a time in the template output. Having trouble doing this, I wondered why type the result is & discovered that, type(blah), tells me that it's a NoneType, even tho it actually has usuable values. That's very puzzling, and I couldn't find anything in the Django documentation about this.
I tried using blah['length'], to get my specific integer, but that didn't work. So it appears the type isn't a dict, set, or list.
So, what type is a Django query result and why does it tell me it's a NoneType even tho it isn't?
I'm really scratching my head on this one.
Django querysets are of their own type. The idea for them is to retrieve sets of objects and for that they provide Django's public queryset API (docs). As a result of that, it's probably not a good idea to change any queryset attributes since there is no guarantee future Django releases will have the same attributes. You can however change attributes of model instances they return:
qs = FooModel.objects.all()
for model in qs:
model.attr = 'foo'
So in your case you can do something like:
# assuming you allow your integer field to be null
class FooModel(models.Model):
time = models.IntegerField(null=True)
qs = FooModel.objects.all()
for model in qs:
# handle case when time is null - model.time will be None
t = model.time or 90
model.time = timedelta(seconds=t)
If however you only need to retreive one object (model instance), then you should probably use get queryset method instead of filter:
foo = FooModel.objects.get(...)
foo.time = timedelta(seconds=foo.time)