Django 'exclude' query did not return expected results - django

I am relatively new to Django and this is my first post in the forum.
Below are the models(simplified) that are used in the app.
The app is about reserving a set of resources for a given period.
from django.db import models
class Resource(models.Model):
id = models.AutoField(primary_key=True)
serialno = models.CharField(max_length=30, null=False, unique=True)
name = models.CharField(max_length=40, null=False)
def __str__(self):
return f"{self.name}/{self.serialno}"
class Reservations(models.Model):
id = models.AutoField(primary_key=True)
active = models.BooleanField(default=True)
name = models.CharField(max_length=30, null=False)
startdate = models.DateField(null=False)
enddate = models.DateField(null=False)
resource = models.ManyToManyField("myapp.Resource", db_table="myapp_resource_reservations", related_name="reservations")
def __str__(self):
return f"{self.name}/{self.startdate}/{self.enddate}"
For example, below are the data present in the models
Resource(format: name/serialno)
>>> Resource.objects.all()
<QuerySet [<Resource: Resource1/RES1>, <Resource: Resource2/RES2>, <Resource: Resource3/RES3>, <Resource: Resource4/RES4>]>
>>>
Reservations(format: name/startdate/enddate/active)
All reservations are made for Resource1
>>> Reservations.objects.all()
<QuerySet [<Reservations: Booking1/2023-03-01/2023-03-07/True>, <Reservations: Booking2/2023-03-15/2023-03-22/True>, <Reservations: BookingX/2023-03-08/2023-03-14/False>]>
>>>
I am trying to retrieve all resources that do not have an 'active' reservation for a given date period using below query.
>>> Resource.objects.exclude((Q(reservations__startdate__range=('2023-03-08','2023-03-14')) | Q(reservations__enddate__range=('2023-03-08','2023-03-14'))) & Q(reservations__active=True))
<QuerySet [<Resource: Resource2/RES2>, <Resource: Resource3/RES3>, <Resource: Resource4/RES4>]>
>>>
Resource1 does have a reservation: BookingX for period 2023-03-08 to 14 but it is active=False. I expected 'Resource1' to show up in above exclude query but it didn't (intended logic: 'exclude all resources that fall in the date range with an active=True reservation').
Can someone help understand why the results are not as expected ? What am I doing wrong ?
Tried using 'filter' instead of 'exclude', it behaves as expected.
>>> Resource.objects.filter((Q(reservations__startdate__range=('2023-03-08','2023-03-14')) | Q(reservations__enddate__range=('2023-03-08','2023-03-14'))) & Q(reservations__active=True))
<QuerySet []>
>>>
>>> Resource.objects.filter((Q(reservations__startdate__range=('2023-03-08','2023-03-14')) | Q(reservations__enddate__range=('2023-03-08','2023-03-14'))) & Q(reservations__active=False))
<QuerySet [<Resource: Resource1/RES1>]>
>>>

You're basically telling the ORM to exclude all Resource's that have ANY active reservations. Because & is evaluated first (before |) it doesn't really matter what is in the ranges as it has already excluded Resource 1 at that point.

Related

Django Models: error when using DateField as ForeignKey

Having an issue when trying to use DateField of a model class as the ForeignKey for another model class, and using default set to today on both classes. The error message is:
django.core.exceptions.ValidationError: ["'self.date' value has an invalid date format. It must be in YYYY-MM-DD format."]
code:
class DailyImage(models.Model):
date = models.DateField(auto_now_add=True, unique=True)
name = models.CharField(max_length = 1000)
image = models.CharField(max_length = 1000)
location = models.CharField(max_length = 1000)
def __str__(self):
return str(self.id) + ": " + self.name + ", " + self.location
class JournalEntry(models.Model):
date = models.DateField(auto_now_add=True)
journal = models.CharField(max_length = 5000)
image = models.ForeignKey(DailyImage, to_field='date', default='self.date')
The site is a daily journal. Each day, it adds an image from unsplash.it to the DailyImage class, which is then displayed as the header on the home page, and header on the page for the journal entry created that day. When a journal entry is created, it should automatically be referenced to the image that was created that day.
testing it in shell, the date fields seem to match, but are formatted as: datetime.date(YYYY, MM, DD)
>>> a = JournalEntry.objects.get(pk=1)
>>> a
<JournalEntry: test>
>>> a.date
datetime.date(2016, 11, 7)
>>> from journal.models import DailyImage as image
>>> b = image.objects.get(pk=1)
>>> b.date
datetime.date(2016, 11, 7)
>>> b.date == a.date
True
Any suggestions to how this should be done properly would be greatly appreciated!
a.date returns the datetime object, but you have to set the format.
t = datetime.date(2016, 11, 7)
t.strftime("%Y-%m-%d")
# '2016-11-07'
You could also set the default datetime format in settings.py
DATETIME_FORMAT = 'Y-m-d'
However I'm not sure that would be a solution in your situation.

ContentType in a model not being saved correctly

I have a model that is essentially a tag:
class Tag( models.Model ):
tag = models.CharField( max_length=15 )
text_color = RGBColorField(default='#000000')
background_color = RGBColorField( default='#88ff88' )
entered_by = models.ForeignKey( 'member.Member', related_name='tag_entered_by' )
entered_on = models.DateTimeField( default=timezone.now )
content_type = models.ForeignKey(ContentType, related_name="tag_content_type")
def __str__(self):
return self.tag
A few days ago I created a few tags associated with a particular model based on the ContentType. Everything was working fine and "I didn't change anything".
I then created some Tags from the shell:
>> class_object=get_model('call', 'Call')
>> ct = ContentType.objects.get_for_model(class_object)
>>> tag = Tag(tag='thetag', text_color='#FFFFFF', background_color='#B40303', content_type=ct, entered_by=automem)
>>> tag.content_type
<ContentType: Call>
>>> tag.save()
>>> tag.content_type
<ContentType: Call>
>>>
>>>
>>> ts = Tag.objects.filter(tag='thetag')
>>> for t in ts:
... print(t.content_type)
...
invoice
The "invoice" is a different model. So, I don't understand why, in this case, the content_type is not being saved properly.
I am using Django 1.8 and Python 3.4.
[Added]
A little more information:
>>> ct
<ContentType: Call>
>>> ct.id
24
But when I do this:
>>> for t in ContentType.objects.all():
... print('{} {}'.format(t, t.id))
I get
...
invoice 24
...
Call 28
...

filter none on django field

I have a model Person which sometimes has nothing in the birth_date field. Here is an example:
(Pdb) dude = Person.objects.get(pk=20)
(Pdb) dude
<Person: Bob Mandene>
(Pdb) dude.birth_date
(Pdb) dude.birth_date == None
True
How do I filter these records that have birth_date == None?
I have already tried the following with no success:
1: "birth_date__isnull" does not work.
Person.objects.filter(birth_date__isnull = True)
# does not return the required
# object. Returns a record that have birth_date set to
# NULL when the table was observed in MySQL.
The following does not return the the record with id 20.
Person.objects.filter(birth_date = "")
How do I filter on a field being None? It seems NULL and None are different. When I see the data using sequel pro (mysql graphical client) I see "0000-00-00 00:00:00", and the following did not work either
(Pdb) ab=Patient.objects.filter(birth_date = "0000-00-00 00:00:00")
ValidationError: [u"'0000-00-00 00:00:00' value has the correct format
(YYYY-MM-DD HH:MM[:ss[.uuuuuu]][TZ]) but it is an invalid date/time."]
model
class Person(models.Model):
person_no = models.IntegerField(primary_key=True)
locationid = models.IntegerField(null=True, db_column='LocationID', blank=True)
address = models.ForeignKey('Address', db_column = 'address_no')
birth_date = models.DateTimeField(null=True, blank=True)
class Meta:
managed = False
db_table = 'person'
NULL in mysql turns into None in python, so what you had with birth_date__isnull=True should be returning those that have birth_date as None.
Hmm I'm not sure why 0000-00-00 00:00:00 is casted into None in this case but you can try to use exact:
Person.objects.filter(birth_date__exact=None)
A bit of list comprehension could come in handy here:
persons = [person for person in Person.objects.all() if not person.birth_date]

How to obtain and/or save the queryset criteria to the DB?

I would like to save a queryset criteria to the DB for reuse.
So, if I have a queryset like:
Client.objects.filter(state='AL')
# I'm simplifying the problem for readability. In reality I could have
# a very complex queryset, with multiple filters, excludes and even Q() objects.
I would like to save to the DB not the results of the queryset (i.e. the individual client records that have a state field matching 'AL'); but the queryset itself (i.e. the criteria used in filtering the Client model).
The ultimate goal is to have a "saved filter" that can be read from the DB and used by multiple django applications.
At first I thought I could serialize the queryset and save that. But serializing a queryset actually executes the query - and then I end up with a static list of clients in Alabama at the time of serialization. I want the list to be dynamic (i.e. each time I read the queryset from the DB it should execute and retrieve the most current list of clients in Alabama).
Edit: Alternatively, is it possible to obtain a list of filters applied to a queryset?
Something like:
qs = Client.objects.filter(state='AL')
filters = qs.getFilters()
print filters
{ 'state': 'AL' }
You can do as jcd says, storing the sql.
You can also store the conditions.
In [44]: q=Q( Q(content_type__model="User") | Q(content_type__model="Group"),content_type__app_label="auth")
In [45]: c={'name__startswith':'Can add'}
In [46]: Permission.objects.filter(q).filter(**c)
Out[46]: [<Permission: auth | group | Can add group>, <Permission: auth | user | Can add user>]
In [48]: q2=Q( Q(content_type__model="User") | Q(content_type__model="Group"),content_type__app_label="auth", name__startswith='Can add')
In [49]: Permission.objects.filter(q2)
Out[49]: [<Permission: auth | group | Can add group>, <Permission: auth | user | Can add user>]
In that example you see that the conditions are the objects c and q (although they can be joined in one object, q2). You can then serialize these objects and store them on the database as strings.
--edit--
If you need to have all the conditions on a single database record, you can store them in a dictionary
{'filter_conditions': (cond_1, cond_2, cond_3), 'exclude_conditions': (cond_4, cond_5)}
and then serialize the dictionary.
You can store the sql generated by the query using the queryset's _as_sql() method. The method takes a database connection as an argument, so you'd do:
from app.models import MyModel
from django.db import connection
qs = MyModel.filter(pk__gt=56, published_date__lt=datetime.now())
store_query(qs._as_sql(connection))
You can use http://github.com/denz/django-stored-queryset for that
You can pickle the Query object (not the QuerySet):
>>> import pickle
>>> query = pickle.loads(s) # Assuming 's' is the pickled string.
>>> qs = MyModel.objects.all()
>>> qs.query = query # Restore the original 'query'.
Docs: https://docs.djangoproject.com/en/dev/ref/models/querysets/#pickling-querysets
But: You can’t share pickles between versions
you can create your own model to store your queries.
First field can contains fk to ContentTypes
Second field can be just text field with your query etc.
And after that you can use Q object to set queryset for your model.
The current answer was unclear to me as I don't have much experience with pickle. In 2022, I've found that turning a dict into JSON worked well. I'll show you what I did below. I believe pickling still works, so at the end I will show some more thoughts there.
models.py - example database structure
class Transaction(models.Model):
id = models.CharField(max_length=24, primary_key=True)
date = models.DateField(null=False)
amount = models.IntegerField(null=False)
info = models.CharField()
account = models.ForiegnKey(Account, on_delete=models.SET_NULL, null=True)
category = models.ForeignKey(Category, on_delete=models.SET_NULL, null=True, blank=False, default=None)
class Account(models.Model):
name = models.CharField()
email = models.EmailField()
class Category(models.Model):
name = models.CharField(unique=True)
class Rule(models.Model):
category = models.ForeignKey(Category, on_delete=models.SET_NULL, blank=False, null=True, default=None)
criteria = models.JSONField(default=dict) # this will hold our query
My models store financial transactions, the category the transaction fits into (e.g., salaried income, 1099 income, office expenses, labor expenses, etc...), and a rule to save a query to automatically categorize future transactions without having to remember the query every year when doing taxes.
I know, for example, that all my transactions with my consulting clients should be marked as 1099 income. So I want to create a rule for clients that will grab each monthly transaction and mark it as 1099 income.
Making the query the old-fashioned way
>>> from transactions.models import Category, Rule, Transaction
>>>
>>> client1_transactions = Transaction.objects.filter(account__name="Client One")
<QuerySet [<Transaction: Transaction object (1111111)>, <Transaction: Transaction object (1111112)>, <Transaction: Transaction object (1111113)...]>
>>> client1_transactions.count()
12
Twelve transactions, one for each month. Beautiful.
But how do we save this to the database?
Save query to database in JSONField
We now have Django 4.0 and a bunch of support for JSONField.
I've been able to grab the filtering values out of a form POST request, then add them in view logic.
urls.py
from transactions import views
app_name = "transactions"
urlpatterns = [
path("categorize", views.categorize, name="categorize"),
path("", views.list, name="list"),
]
transactions/list.html
<form action="{% url 'transactions:categorize' %}" method="POST">
{% csrf_token %}
<label for="info">Info field contains...</label>
<input id="info" type="text" name="info">
<label for="account">Account name contains...</label>
<input id="account" type="text" name="account">
<label for="category">New category should be...</label>
<input id="category" type="text" name="category">
<button type="submit">Make a Rule</button>
</form>
views.py
def categorize(request):
# get POST data from our form
info = request.POST.get("info", "")
account = request.POST.get("account", "")
category = request.POST.get("category", "")
# set up query
query = {}
if info:
query["info__icontains"] = info
if account:
query["account__name__icontains"] = account
# update the database
category_obj, _ = Category.objects.get_or_create(name=category)
transactions = Transaction.objects.filter(**query).order_by("-date")
Rule.objects.get_or_create(category=category_obj, criteria=query)
transactions.update(category=category_obj)
# render the template
return render(
request,
"transactions/list.html",
{
"transactions": transactions.select_related("account"),
},
)
That's pretty much it!
My example here is a little contrived, so please forgive any errors.
How to do it with pickle
I actually lied before. I have a little experience with pickle and I do like it, but I am not sure on how to save it to the database. My guess is that you'd then save the pickled string to a BinaryField.
Perhaps something like this:
>>> # imports
>>> import pickle # standard library
>>> from transactions.models import Category, Rule, Transaction # my own stuff
>>>
>>> # create the query
>>> qs_to_save = Transaction.objects.filter(account__name="Client 1")
>>> qs_to_save.count()
12
>>>
>>> # create the pickle
>>> saved_pickle = pickle.dumps(qs_to_save.query)
>>> type(saved_pickle)
<class 'bytes'>
>>>
>>> # save to database
>>> # make sure `criteria = models.BinaryField()` above in models.py
>>> # I'm unsure about this
>>> test_category, _ = Category.objects.get_or_create(name="Test Category")
>>> test_rule = Rule.objects.create(category=test_category, criteria=saved_pickle)
>>>
>>> # remake queryset at a later date
>>> new_qs = Transaction.objects.all()
>>> new_qs.query = pickle.loads(test_rule.criteria)
>>> new_qs.count()
12
Going even further beyond
I found a way to make this all work with my htmx live search, allowing me to see the results of my query on the front end of my site before saving.
This answer is already too long, so here's a link to a post if you care about that: Saving a Django Query to the Database.

filter from an object and more object that related in django?

This Is my models.py
class Customer(models.Model):
def __unicode__(self):
return self.name
name = models.CharField(max_length=200)
type = models.ForeignKey(Customer_Type)
active = models.BooleanField(default=True)
class Sale(models.Model):
def __unicode__(self):
return "Sale %s (%i)" % (self.type, self.id)
customer = models.ForeignKey(Customer)
total = models.DecimalField(max_digits=15, decimal_places=3)
class Unitary_Sale(models.Model):
book = models.ForeignKey(Book)
quantity = models.IntegerField()
unit_price = models.DecimalField(max_digits=15, decimal_places=3)
sale = models.ForeignKey(Sale)
Views.py
def get_filter_result(self, customer_type='' volume_sale=''):
qdict = {}
if customer_type != '':
qdict['type__name'] = customer_type
qdict['active']=True
#WHAT I AM GOING TO DO NEXT
*** if volume_sale != '':
pass # This point I am asking :)
#IT RETURN CUSTOMERS BASE ON PARAMS.
queryset = Customer.objects.filter(**qdict)
***The volume_sale is:
units=Unitary_Sale.objects.all()
>>> units=Unitary_Sale.objects.all()
>>> for unit in units:
... print unit.sale.customer
... print unit.book,unit.sale.total
...
Sok nara
Khmer Empire (H001) 38.4
Sok nara
killing field (H001) 16
San ta
khmer krom (H001) 20
San ta
Khmer Empire (H001) 20
>>>
{<Customer: Sok nara>: Decimal("56.4"), <Customer: san ta>: Decimal("40")}
Decimal("56.4") , Decimal("40") this is the volume_sale
I could not find the ways to make the filter from difference object as in my case.
It will be great if everyone here help in this stuck? Thanks.
Cool, this actually pretty easy to implement. I used the django annotation feature documented here and here:
from django.db.models import Sum
query = Customer.objects.all().annotate(volume_sale = Sum('Sale__total'))
query.filter(volume_sale < 12.0) #return all customers that have purchased less than 12.0
query[0].volume_sale #you can even get it on the Customer objects given back
Django will take care of the database joins for you. It will put this extra field into each instance of the model that you can filter, order_by and access in templates or views.