How to write a OR query when number of parameters may change? - django

I need to make a filter to show BookInstances with following conditions:
BookInstance.public = True
BookInstance.owner != current logged in user
BookInstance.book.title.icontains('some text')
BookInstance.book.description.icontains('some text')
BookInstance.book.authors(id_in=some list of ids)
BookInstance.book.categories(id_in=some list of ids)
The conditions will be combined as:
1 AND 2 AND ( ( 3 OR 4 ) OR 5 OR 6 )
3 & 4 use the same text for search.
current scaffolding in view:
searchedObjects = BookInstance.objects.filter(public=True)
if request.user.is_authenticated:
searchedObjects = searchedObjects.exclude(owner=request.user)
filterObj = dict(request.POST)
for key in filterObj:
if key == 'bookTitleOrDescription':
#condition 3 & 4
bookTitleOrDescription = filterObj[key][0]
elif key == 'author[]':
#condition 5
authorList = filterObj[key]
elif key == 'category[]':
#condition 6
categoryList = filterObj[key]
searchedObjects will have the query result.
The if, elif are required as some parameters may or may not be present. We must avoid writing 10 combinations for it.
models:
class Author(SafeDeleteModel):
name = models.CharField(max_length=255)
class Category(SafeDeleteModel):
title = models.CharField(max_length=255)
class Book(SafeDeleteModel):
title = models.CharField(max_length=255)
description = models.CharField(max_length=255)
authors = models.ManyToManyField(Author)
categories = models.ManyToManyField(Category)
class BookInstance(SafeDeleteModel):
owner = models.ForeignKey(User, on_delete=models.CASCADE, null=True)
book = models.ForeignKey(Book, on_delete=models.CASCADE)
public = models.BooleanField(verbose_name='Will show in search ?')
lendable = models.BooleanField(verbose_name='In good condition ?')

You can filter your query for each field like this:
author = request.POST.get('author', None)
if author:
searchedObjects = searchedObjects.filter(author=author)

You can do it in following way:
filter = Q()
if key == 'category[]':
filter = filter | Q(book__category__id__in='some list of ids')
if key == 'author[]':
filter = filter | Q(book__authors__id__in='some list of ids')
....
....
data = BookInstance.objects.filter(filter)

Related

Django query for multiple fields with list values

I have below Django model
class Post(models.Model):
author = models.ForeignKey(CustomUser,on_delete=models.CASCADE,)
title = models.CharField(max_length=200,null=True)
text = models.TextField()
post_url = models.URLField(max_length = 200, blank = True)
post_tier = models.ForeignKey(Tiers, on_delete=models.CASCADE, null= True)
post_tier_value = models.IntegerField(default=0)
slug = models.SlugField(unique=True, blank=True)
I have author_list = [author1#gmail.com, author2#gmail.com] and tier_value_list = [2000,3000]. I want to query in the above model for (author = author1#gmail.com and post_tier_value <= 2000) or (author = author2#gmail.com and post_tier_value <= 3000).
Can someone help me how to make query for this?
Use Q() operator
from django.db.models import Q
author_list = ['author1#gmail.com', 'author2#gmail.com']
tier_value_list = [2000, 3000]
query = Q()
for author, value in zip(author_list, tier_value_list):
query = query | Q(author=author, post_tier_value__lte=value)
Post.objects.filter(query)
Note: I have used author__email which is a nested lookup since the author_list contain the email_ids

how to write a method in order to change database directly in Django model

I made a Product class in Django models like :
class Product(models.Model):
title = models.CharField(max_length=255, unique = True)
description = models.TextField()
image_url = models.URLField(verify_exists=True, max_length=200, blank = True, null = True)
quantity = models.PositiveSmallIntegerField(default=0)
And i want to add a sell() method inside this class. well i did like:
def sell(self):
result = self.quantity - 1
return result
I want to change the value in database when just execute P.sell().
when i run it in the shell like this
>>p = Product(title = 'title', description = 'des', quantity = 10)
>>p.save()
>>p.sell()
9 # it shown 9, but the database not changed still 10
but if i continue to do like this
>> p.quantity = p.sell()
>> p.save()
it can change the quantity to 9
but how can i just change the value when i just type p.sell() ?
how could i edit it in models?
Uh...
def sell(self, save=True):
self.quantity -= 1
if save:
self.save()

Django query with simple arithmetic among model fields and comparison with field from another model

(All code below is a simplified representation of the actual code).
If I have the following Django models: -
class Material(models.Model):
name = models.CharField(max_length=110)
class OrderDetail(models.Model):
material = models.ForeignKey(Material)
order_quantity = models.IntegerField()
quantity_delivered = models.IntegerField(null=True, blank=True)
is_open = models.BooleanField()
is_active = models.BooleanField()
class MaterialRequirement(models.Model):
material = models.ForeignKey(Material)
quantity = models.IntegerField()
I have with me an instance of the MaterialRequirement model, let's call it material_requirement. I am trying to retrieve OrderDetail objects which satisfy the following criteria:-
1. material = material_requirement.material
2. is_open = True
3. is_active = True
4. order_quantity > quantity_delivered
5. material_requirement.quantity <= order_quantity - quantity_delivered
My question is how do I incorporate the 5th selection criteria in my ORM query below.
order_details = OrderDetail.objects.select_related('material').filter(material=material_requirement.material,
is_open=True,
is_active=True,
order_quantity__gt=F('quantity_delivered'))
order_details = (OrderDetail.objects.select_related('material')
.filter(material=material_requirement.material,
is_open=True,
is_active=True,
order_quantity__gt=F('quantity_delivered'),
quantity_delivered__gte=F('order_quantity') - material_requirement.quantity,
)
)
order_details = order_detail.extra(where=["order_quantity - quantity_delivered <= %d" % material_requirement.quantity]))

Django: show external data in model based form

I have some external data (SOAP) that I want to show in a model-based-form.
The Model:
class UserProfile(User):
profile_email = models.EmailField()
company_name = models.CharField()
coc_number = models.CharField()
gender = models.CharField()
#etc
The Form:
class UserDetailsForm(forms.ModelForm):
class Meta:
model = UserProfile
The data is a dictionary:
u = {}
u['profile_email'] = 'monkey'
u['company_name'] = 'tiger'
u['coc_number'] = 'some number'
u['gender'] = 'M'
My question is: What is the best way to put the data into the form? What I have so far:
form = UserDetailsForm(initial=u)
This results in a form with all the data.
1) But is this the right way to fill a model-bases-form with external data?
2) How can I set the right value in a select option (Choose country for instance)?
Yes, this is appropriate way.
You need to set value for select/choices field in the dict similar to approach 1.
For example:
COUNTRY_CHOICES = (
('IN', 'India'),
('US', 'USA'),
)
....
#model field
country = models.CharField(choices=COUNTRY_CHOICES)
# then set it in dict as
u = {}
u['country'] = 'IN'
u['profile_email'] = 'monkey'
u['company_name'] = 'tiger'
u['coc_number'] = 'some number'
u['gender'] = 'M'
...

How do I populate related Django models when using custom SQL in a custom manager?

I have some things I need to use custom SQL for. Just to start, I don't want to get into the discussion of why we're using custom SQL. That's old and irrelevant here.
Anyway, with this custom SQL, I'm populating the model and attempting to populate a related/sub model. That all seems to go fine, until that related model is accessed within a view or template. If I do that, django initiates another SQL query, which is totally unnecessary as I already got that data out with the first query.
Here are example, trimmed up models:
class Preferredname(models.Model):
preferredname_id = models.CharField(primary_key=True, max_length=1)
name = models.CharField(max_length=255)
class PersonCustom(models.Manager):
def getSpecial(self):
from django.db import connection, transaction
curse = DictCursor(connection.cursor())
curse.execute("SELECT * FROM person WHERE special = 't'")
result_list = []
for row in curse.fetchall():
i = self.model(
firstname = row['firstname'],
middlename = row['middlename'],
nickname = row['nickname'],
lastname = row['lastname'],
suffix = row['suffix'],
)
i.preferredname = Preferredname()
i.preferredname.preferredname_id = row['preferredname_id']
result_list.append(i)
return result_list
class Person(models.Model):
person_id = models.IntegerField(primary_key=True)
firstname = models.CharField(max_length=255, blank=True)
middlename = models.CharField(max_length=255, blank=True)
lastname = models.CharField(max_length=255, blank=True)
nickname = models.CharField(max_length=255, blank=True)
suffix = models.CharField(max_length=255, blank=True)
preferredname = models.ForeignKey(Preferredname)
objects = PersonCustom()
def get_preferred_name(self):
try:
if self.preferredname.preferredname_id == 'N':
pref = self.nickname
elif self.preferredname.preferredname_id == 'M':
pref = self.middlename
else:
pref = self.firstname
except ObjectDoesNotExist:
if self._preferred_name == 'N':
pref = self.nickname
elif self._preferred_name == 'M':
pref = self.middlename
else:
pref = self.firstname
name = "%s %s %s" % (pref, self.lastname, self.suffix)
return name.strip()
def set_preferred_name(self, val):
self._preferred_name = val
preferred_name = property(get_preferred_name, set_preferred_name)
As an example:
>>> p = Person.objects.getSpecial()
>>> p[0].preferred_name
Since the get_preferred_name() method accesses self.preferredname.preferredname_id(which in theory, should already be populated) it makes another query for preferredname.
Am I off base here, or should this work as I intend? If I'm off here, is there a way to populate related models from within a custom manager?
Rather than assigning your object to i.preferredname, try setting it to i._preferredname_cache, which is the internal object Django uses to cache foreign key lookups. That should work.