How to Call django model field name from a list? - python

django models.py
class mymodel(models.Model):
date_check = models.DateField()
item_1 = models.NullBooleanField()
item_2 = mod`enter code here`els.NullBooleanField()
item_3 = models.NullBooleanField()
item_4 = models.NullBooleanField()
item_5 = models.NullBooleanField()
Task:
>>> from .models import mymodel
>>>
>>> a_list = ['item_1', 'item_2', 'item_3', 'item_4', 'item_5', 'item_5', ]
>>> a_year = 2018
>>> param1 = {}
>>> param2 = {}
>>> param3 = {}
>>> for item in a_list :
>>> param1[item] = mymodel.objects.filter(date_check__year = a_year, item=True).count()
>>> param2[item] = mymodel.objects.filter(date_check__year = a_year, item=False).count()
>>> param3[item] = mymodel.objects.filter(date_check__year = a_year, item=None).count()
.....error here
how do we call field name from list?

Here item is interpreted as an identifier, so the name of the parameter is item. The fact that there is a variable with the same name and a certain value, is not relevant.
We can however fix this by constructing a dictionary that maps item (so here it will be replaced with the corresponding value) to True, False, and None. We the can use this dictionary as the named parameters dictionary* by using two consecutive asterisks as prefix:
for item in a_list :
param1[item] = mymodel.objects.filter(date_check__year=a_year, **{item: True}).count()
param2[item] = mymodel.objects.filter(date_check__year=a_year, **{item: False}).count()
param3[item] = mymodel.objects.filter(date_check__year=a_year, **{item: None}).count()
Note that this will hoever result in multiple queries. We can reduce the amount of querying by for example aggregating over a selection, like:
qs = mymodel.objects.filter(date_check__year=a_year)
for item in a_list :
data = qs.aggregate(
p1=Count('pk', filter=Q(**{item: True}))
p2=Count('pk', filter=Q(**{item: False}))
p3=Count('pk', filter=Q(**{item: None}))
)
param1[item] = data['p1'] or 0
param2[item] = data['p2'] or 0
param3[item] = data['p3'] or 0
Here we thus count three things per query: the number of Trues, Falses and Nones. We can actually extend this logic, and even count everything in a single query, which is typically more efficient (this is not per se true, but making a query typically results in some overhead to construct a query, interpret the query at the database side, serializing results, etc.).

Related

icontains and getlist django python

We are trying to return a list of titles for the Django API, in which the title can have a few keywords.
So for instance, if we use the __icontains method to search for "money" and "world" (api.com/?keyworld=money&keyword=world) this will return all records that contain money, world or both.
The related SQL statement is:
select * from news
where news_source = 1 or news_source = 2
and news_title like '%money%' or news_title like '%world%'
We are trying to use this code to allow the user to have multiple keywords for the __icontains as well as multiple sources, so the end goal is:
api.com/?keyworld=money&keyword=world&source=1&source=2
Our code:
def get_queryset(self):
queryset = News.objects.all()
title = self.request.query_params.getlist('title')
source = self.request.query_params.getlist('source')
if title:
queryset = queryset.filter(news_title__icontains=title, news_source__in=source)
return queryset
The issue is that this is only returning the second keyword if a second keyword is used, and not other keywords prior to what is typed in &keyword=.
You can not perform an __icontains with a list, but you can for example design a function that, for a list constructs the logical or of these values. For example:
from django.db.models import Q
from functools import reduce
from operator import or_
def or_fold(list_of_qs):
if list_of_qs:
return reduce(or_, list_of_qs)
else:
return Q()
def unroll_lists_or(qs, **kwargs):
return qs.filter([
or_fold(Q(**{k: vi}) for vi in v)
for k, v in kwargs.items()
])
You can then call the unroll_lists_or with a queryset, and each item should be an iterable (for example a list). It will then perform or-logic between the items of the list, and and-logic between different keys. In case an iterable is empty, it is ignored.
So we can then write the check as:
unroll_lists_or(queryset, news_title__icontains=title, news_source=source)
In case title contains two items (so title == [title1, title2]), and source contains three items (so source = [source1, source2, source3]), then this will result in:
qs.filter(
Q(news_title__icontains=title1) | Q(news_title__icontains=title2),
Q(news_source=source1) | Q(news_source=source2) | Q(news_source=source3)
)
You can however combine it with an .filter(..) for the __in check. For example:
queryset = News.objects.all()
if source:
queryset = queryset.filter(news_source__in=source)
queryset = unroll_lists_or(queryset, news_title__icontains=title)
I was able to solve this by creating 2 separate functions within the get_querset() function, which is called when a GET request is made.
def get_queryset(self):
queryset = News.objects.all()
source_list = self.request.query_params.getlist('source')
keyword_list = self.request.query_params.getlist('title')
if source_list or keyword_list:
def create_q_source(*args):
list = [*args]
source = Q()
for value in list:
source.add(Q(news_source=value), Q.OR)
return source
def create_q_keyword(*args):
list = [*args]
keyword = Q()
for value in list:
keyword.add(Q(news_title__icontains=value), Q.OR)
return keyword
queryset = queryset.filter(create_q_source(*source_list),create_q_keyword(*keyword_list))
return queryset
Edit:
When you go to the api link and pass in the parameters, filtering will occur based on what is passed in:
http://127.0.0.1:8000/api/notes/?keyword=trump&keyword=beyond&keyword=money&source=1
SQL Equivalent:
select * from news where news_source = 1 AND news_title like '%beyond%' OR news_title like '%money%'

django - can't assign a foreign key

For unknown reasons, I cannot assign a foreign key instance of Item_rarity table into Detailed_item table. Django throws an error:
Cannot assign "u'Basic'": "Detailed_item.rarity" must be a "Item_rarity" instance.
... But in Item_rarity dictionary "Basic" record exists - I can choose it from admin panel and create Detailed_item record manually.
I have defined models:
class Detailed_item(models.Model):
item_id = models.IntegerField(primary_key=True)
name = models.CharField(max_length=50)
level = models.IntegerField()
icon = models.CharField(max_length=150)
rarity = models.ForeignKey('Item_rarity')
general_type = models.ForeignKey('Item_type')
detailed_type = models.ForeignKey('Item_detailed_type')
class Item_rarity(models.Model):
name = models.CharField(max_length=15, primary_key=True)
class Item_type(models.Model):
name = models.CharField(max_length=15, primary_key=True)
class Item_detailed_type(models.Model):
name = models.CharField(max_length=20, primary_key=True)
In views, I try to populate it in this manner (inserting multiple items):
...
items = get_all_items() #get dict of items
for element in items:
tmp_det_type = ''
for key, val in element.iteritems():
#get 'detailed type' from inner dict
if key == "type":
tmp_det_type = val
item = Detailed_item(
item_id=element['id'],
name=element['name'],
level=element['level'],
icon=element['icon'],
rarity=element['rarity'], #error
general_type=element['type'],
detailed_type=tmp_det_type,
)
item.save()
...
I even tried to hard code "Basic" string, but it doesn't work either.
* Solved *
Next two entries, that is Item_type and Item_detailed_type were also invalid.
Correct code:
from app.models import Detailed_item, Item_rarity, Item_type, Item_detailed_type
...
items = get_all_items() #get dict of items
for element in items:
tmp_det_type = ''
for key, val in element.iteritems():
#get 'detailed type' from inner dict
if key == "type":
tmp_det_type = val
#create objects with string values
obj_rarity = Item_rarity(name=element['rarity'])
obj_item_type = Item_type(name=element['type'])
obj_item_detailed_type = Item_detailed_type(name=tmp_det_type)
item = Detailed_item(
item_id=element['id'],
name=element['name'],
level=element['level'],
icon=element['icon'],
rarity=obj_rarity,
general_type=obj_item_type,
detailed_type=obj_item_detailed_type,
)
item.save()
...
Item_rarity instance should be passed while storing Detailed_item object since Item_rarity is a foreign key related object in Detailed_item.
Its that you might have passed the Basic string instead of the <Basic Object> itself.
While creating an object in django using its ORM, any foreign_key related object should be provided with the instance itself instead of the id(pk) of the object, where as while fetching the data from the database you can use either of instance or the id(pk) of the instance.
class ParentModel(models.Model):
model_field = models.CharField(max_length=16)
class MyModel(models.Model):
some_field = models.ForeignKey('ParentModel')
parent_model = ParentModel.objects.create(model_field='some_data')
my_model = MyModel.objects.create(some_field=parent_model)
^^^^^^^^^^^^
Note here that the parent_model object itself is passed instead of the id
While fetching the data back,
parent_model = ParentModel.objects.get(model_field='some_data')
my_model = MyModel.objects.get(some_field=parent_model)
or
my_model = MyModel.objects.get(some_field=parent_model.id)
Both would work in case of data fetch.
You do not have to provide the related object on creation if you change the kwarg in to rarity_name:
item = Detailed_item(
item_id=element['id'],
name=element['name'],
level=element['level'],
icon=element['icon'],
rarity_name=element['rarity'], # no error
general_type=element['type'],
detailed_type=tmp_det_type,
)
I have only tested this with the regular id field (the auto pk) but it
should work with your primary key just fine.
E.g.
class SimpleModel(Model):
value = TextField(blank=True)
class ComplexModel(Model):
simple = ForeingKey(SimpleModel)
title = TextField(unique=True)
ComplexModel.objects.create(title='test', simple_id=1)

GAE Search: Relationships between documents in index

I'd like to return a result object which contains the indexed document AND other information, from another entity, with which the indexed document has a relationship.
So, let's say I have two Kinds:
class Store(BaseHandler):
store_name = ndb.StringProperty()
logo_url = ndb.StringProperty()
about_store = ndb.TextProperty()
class Product(BaseHandler):
product_name = ndb.StringProperty
store_key = ndb.KeyProperty() #Store entity which created this product.
Then, I add each new Product entity to the index, like this:
class NewProduct(BaseHandler):
def get(self, store_id):
self.render('new-product.html')
def post(self, store_id):
product_name = self.request.get('product_name')
store_key = ndb.Key('Store', store_id)
try:
p = Product(
store_key = store_key,
product_name = product_name)
p.put()
# Add p to index
p_doc = search.Document(
doc_id = str(p.key.id()),
fields = [
search.AtomField(name = 'store_id', value = str(str_id)),
search.TextField(name = 'product_name', value = e.product_name)])
index = search.Index('product_index')
index.put(p_doc)
except:
# handle error
Now, if I run a search query using...
index = search.Index('product_index')
index.search('PRODUCT_NAME')
I should be able to return all the Product documents from the index by its query string.
My question is: How do I efficiently return a result object which contains both the product document AND its Store kind information (store_name, logo_url, about_store)?

How do I construct an AND query on the same field in the URL of TastyPie?

I want to filter results in the tastypie to get results that conform to both of two filters on the same field.
So if I have a simple model like this...
class Item(models.Model):
name = models.CharField(max_length=255)
description = models.TextField()
With a ModelResource...
class ItemResource(ModelResource):
...
class Meta():
queryset = Item.objects.all()
resource_name = 'item'
filtering = {'name': ALL, 'description': ALL}
I can easily construct 'AND' queries in the url of tastypie:
/api/v1/item/?name__contains=hello&description__contains=foo
But if I want to construct an AND operator on the same field, it only takes the second argument and ignores the first. That is,
/api/v1/item/?name__contains=hello&name__contains=world
returns resources whose name field contains 'world' but not those whose name field contains BOTH 'hello' and 'world'.
I understand how to do this directly in django:
Item.objects.filter(name__contains='hello').filter(name__contains='world')
But how do I construct this kind of a query in the URL of the tastypie?
I'm using the below. It will give you support for name__contains=hello,world. And you could also do negations name__contains!=foo.
def build_filters(self, filters=None):
"""
Adds support for negation filtering
"""
if not filters:
return filters
applicable_filters = {}
self.filters = filters
# Normal filtering
filter_params = dict([(x, filters[x]) for x in filter(lambda x: not x.endswith('!'), filters)])
applicable_filters['filter'] = super(MainBaseResource, self).build_filters(filter_params)
# Exclude filtering
exclude_params = dict([(x[:-1], filters[x]) for x in filter(lambda x: x.endswith('!'), filters)])
applicable_filters['exclude'] = super(MainBaseResource, self).build_filters(exclude_params)
return applicable_filters
def apply_filters(self, request, applicable_filters):
"""
Adds support for:
1. negation filtering: value_date__year!=2013
2. multiple filtering value_date__year=2013,2012
"""
from django.db.models import Q
import operator
from types import *
objects = self.get_object_list(request)
f = applicable_filters.get('filter')
if f:
# Q Filters for multiple values (1,2,3 etc)
q_filters = []
for key, val in f.iteritems():
string = str(val)
if ',' in string:
for excl_filter in string.split(','):
q_filters.append((key, excl_filter))
q_list = [Q(x) for x in q_filters]
for x in q_filters:
try:
del f[x[0]]
except:
pass
if q_list:
objects = objects.filter(reduce(operator.or_, q_list), **f)
else:
objects = objects.filter(**f)
e = applicable_filters.get('exclude')
if e:
objects = objects.exclude(**e)
return objects

counting/filtering database-entries over multiple foreign key-relations

These are my DB-Models:
class Category(models.Model):
name = models.CharField(max_length = 20, unique = True)
...
class Feed(models.Model):
title = models.CharField(max_length = 100)
category = models.ForeignKey(Category)
...
class Article(models.Model):
title = models.CharField(max_length = 100)
read = models.BooleanField(default = False)
feed = models.ForeignKey(Feed)
...
Every Article belongs to one Feed (source) and each Feed is in a Category.
Now, i want to create a view to display all categories with some meta-information,
e.g. how many unread articles are in category x.
I tried things like this, but nothing worked:
categories = Category.objects.filter(feed__article__read=False)\
.annotate(Count('feed__article'))
What is the proper way to extract those information?
Especially if i want to add further information like: number of feeds in category and
number of favored articles in one QuerySet (If possible)...
Any ideas?
Thanks.
EDIT: Since i had no idea how to 'solve' this problem, i've written an ugly workaround:
result = categories.values_list('name',
'feed__title',
'feed__article__title',
'feed__article__read')
for i in range(0, len(result)):
#if pointer changed to a new category
#dump current dict to list and clear dict for the new values
if last != result[i][0]:
category_list.append(category_info.copy())
category_info.clear()
last = result[i][0]
if some values None:
insert values
elif some other values None:
insert values
else:
category_info['name'] = result[i][0]
category_info['feed_count'] = category_info.get('feed_count', 0) + 1
category_info['all_article_count'] = category_info.get('all_article_count', 0) + 1
#if a article has not been read yet
if result[i][3] == False:
category_info['unread_article_count'] = category_info.get('unread_article_count', 0) + 1
#if this category is the last in the result-list
if i+1 == len(result):
category_list.append(category_info.copy())
i += 1
I am pretty sure there is a quicker and nicer way to get those information, but at least i can work with it for the moment :/
You must label the information. You should be able to use category.article_count for the items in the queryset if you use the query below.
categories = Category.objects.filter(feed__article__read=False)\
.annotate(article_count=Count('feed__article'))

Categories

Resources