Let's say I've defined this model:
class Identifier(models.Model):
user = models.ForeignKey(User)
key = models.CharField(max_length=64)
value = models.CharField(max_length=255)
Each user will have multiple identifiers, each with a key and a value. I am 100% sure I want to keep the design like this, there are external reasons why I'm doing it that I won't go through here, so I'm not interested in changing this.
I'd like to develop a function of this sort:
def get_users_by_identifiers(**kwargs):
# something goes here
return users
The function will return all users that have one of the key=value pairs specified in **kwargs. Here's an example usage:
get_users_by_identifiers(a=1, b=2)
This should return all users for whom a=1 or b=2. I've noticed that the way I've set this up, this amounts to a disjunctive normal form...the SQL query would be something like:
SELECT DISTINCT(user_id) FROM app_identifier
WHERE (key = "a" AND value = "1") OR (key = "b" AND value = "2") ...
I feel like there's got to be some elegant way to take the **kwargs input and do a Django filter on it, in just 1-2 lines, to produce this result. I'm new to Django though, so I'm just not sure how to do it. Here's my function now, and I'm completely sure it's not the best way to do it :)
def get_users_by_identifiers(**identifiers):
users = []
for key, value in identifiers.items():
for identifier in Identifier.objects.filter(key=key, value=value):
if not identifier.user in users:
users.append(identifier.user)
return users
Any ideas? :)
Thanks!
def get_users_by_identifiers(**kwargs):
q = reduce(operator.or_, Q(identifier__key=k, identifier__value=v)
for (k, v) in kwargs.iteritems())
return User.objects.filter(q)
Related
I have a lot of models in my Django project, and many of them need their own queries for specific pages. While working on the seperation of concerns, I feel like there should be a way to make one (or very few) methods that are generic enough and work with input based to work as queries for ALL models.
Say I have model A B and C and I have the following sets of queries:
A.objects.filter(id=object_id)
A.objects.filter(id=object_id).values("id", "name")
A.objects.filter(name="test")
B.objects.filter(relationA=A)
B.objects.filter(id__in=list_of_ids)
C.objects.all()
C.objects.all().exclude('last_name')
Is there any way to create a query by a given:
Model Name (A, B, C)
filter type (possibly, least important if this isn't doable? filter, all)
comparison field (id, name, relation)
comparison type (x=x, x__in=list, x__lte=10)
comparison value (object_id, "test", list_of_ids)
So for example, X being dynamic parts would result in a function (Pseudocode) like:
def query(model, filter_type, comparison_field, comparison_type, comparison_value):
#X.objects.X(X=X)
return model.objects.filter_type(comparison_field + comparison=comparison_value)
When trying it briefly, I immediately ran into the issue of comparison_field not doing what I need it to. Seeing Q objects being mentioned a lot on SO, are they something I should be applying here? and if so, how?
Edit:
As suggested by Klaus D. , I've implemented kwargs with a dynamic dictionary.
query_manager.py
def make_query_kwargs(kwargs_dict, field, value):
kwargs_dict[field] = value
return kwargs_dict
def make_object_query(model, kwargs, columns=[]):
return model.objects.filter(**kwargs).values(*columns)
Which I use by doing the following:
kwargs = {}
id_list = [1, 2, 3, 4]
kwargs = query_manager.make_query_kwargs(kwargs, "id__in", id_list)
query_result = query_manager.make_object_query(A, kwargs)
Working on this for learning experience. The 3 ideas below I came up with
A) User creates a profile so I have a dictionary for fname and lname.
B)Then I randomly generate userid add that to a list. This list only contains random user id that I will user later eg: userid012,userid89
C) I assign A and B in a new dictionary. Output looks like this:
used_id user3
profile {'lastname': 'jon', 'firstname': 'jme'}
problem: I only see the last values user id and names. If I have more than 2 entries, I do not see the 1st ones. Helpful hint would be really helpful.
Thank You.
import random
print('Enter choice 1-3:'),'\n'
print('', '1-Create new profile','\n',
'2-Update existing profile','\n',
'3-Delete a profile')
#global variables
choice=int(input())
user_directory={}
#Dictionary function that takes fst and lst name and puts in a dict:
def new_profile():
new_profile={}
fn=input('First name:')
new_profile['firstname']=fn
ln = input('Last name:')
new_profile['lastname'] = ln
for k,v in new_profile.items():
new_profile[k]=v
return new_profile
#Generates a random user id which we will assign to a user created above
def user_id():
uid_list=[]
user_id='user'+str(random.randint(0,101))
uid_list.append(user_id)
if(user_id in uid_list):
uid_list.remove(user_id)
user_id = 'user' + str(random.randint(0, 101))
uid_list.append(user_id)
return user_id
#This dictionary will have user id and associate created new_profile
def addToDict():
#user_directory={} unable to use this making it global
user_directory['used_id']=user_id()
user_directory['profile']=new_profile()
for key,value in user_directory.items():
user_directory[key]=value
return user_directory
if(choice==1):
# myuser=addToDict() this appraoch did not work
#addToDict>> adding it here will not get this option in while loop, put inside while
while True:
addToDict()
print('Add another entry?')
choice=input()
#Put the line below to see if number increases
print('Current', len(user_directory)-1)
if(choice!='stop'):
continue
else:
break
for k,v in user_directory.items():
print(k,v)
Bad indentation in the last line of new_profile(). The return is running on the first iteration. Try:
for k,v in new_profile.items():
new_profile[k]=v
return new_profile
Btw, you don't seem to be following most conventions/standards in Python. Take a look at this simple tutorial about PEP, the official style guide. This way you can make better looking code and we can help faster :)
Your code contains a couple of bugs. I can only guess what you want to do. Lets start with the obvious: The function addToDict() should probably add a new user to the dictionary.
What you usually want is to have a dictionary which maps a user_id onto a profile:
def addUserToDict(user_dictionary, user_id, profile):
user_directory[user_id] = profile
And then in the input loop below you call this function with your dictionary, a new user id and a new profile.
A second bug is in user_id(): You always return a list with one new element, with a new random user id. And you always discard the first generated user id and then you add a second one.
I have a class which looks like this.
class CharInStageList(object):
def __init__(self, charid, charname) :
self.charid = charid
self.charname = charname
into this class I would like to add lists that I have.
I know how to do it the normal way
charOne = CharInStageList(1,'Tim')
charTwo = CharInStageList(2,'Struppi')
that's not a problem what I actually want to do is to add them by using a loop.
I get my data in this form
((1,'Tim'),(4,'Struppi'))
or
((1,'Tim'),(4,'Struppi'),(5,'Nami'),(6,'Luffy'))
the amount of characters I have in the scene is always different.
what I imagined would be a loop like this
charlist = ((1,'Tim'),(4,'Struppi'))
for char in charlist
objname = CharInStageList(char[0],char[1])
something like this
I want the objname to change by itself for every object I add to the class.
How can I get this effect?
I can only use python 2.6.6 for this since it's the maya 2013 python
Edit:
Thx #silas-ray #chepner #theodox I looked into Dicts a bit more and that's pretty much what I need
I use a modified version of #chepner method on it.
object_dict = dict( (y, CharInStageList(x,y)) for x,y in data )
Works like a charm
My testcode looks like this
import maya.cmds as cmds
dicttest = {}
def getdata ():
global dicttest
data = ((1,'Tim'),(4,'Struppi'),(5,'Nami'),(6,'Luffy'))
dicttest = dict( (y,(x,y)) for x,y in data )
getdata()
def printtest() :
for char in dicttest:
print dicttest[char]
printtest()
dicttest.clear()
I would have liked to comment in your answers with code examples but I can't get that to work there.
Objects are not added to a class. Instead, you can create a list of objects which are all instances of the same class, using a list comprehension and taking advantage of the *args syntax:
data = ((1,'Tim'),(4,'Struppi'),(5,'Nami'),(6,'Luffy'))
object_list = [ CharInStageList(*x) for x in data ]
Perhaps you want a dictionary instead:
object_dict = dict( (y, CharInStageList(x,y)) for x,y in data )
(Note that CharInStageList is a poor name for the class, because it's not a list; it encapsulates a single character.)
If you really want CharInStateList to be a collection of characters, try something like this, which is just a thin wrapper around a dictionary:
# Your former CharInStageList
class Character(object):
def __init__(self, charid, charname) :
self.charid = charid
self.charname = char name
class OnStageCharacters(object):
def __init__(self):
self.characters = dict()
# Index on-stage characters using their charid
def add(self, character):
self.characters[character.charid] = character
on_stage = OnStageCharacters()
for id, name in data:
on_stage.add( Character(id, name) )
You can't (at least not without hacking at locals/globals, which is generally not a good idea) change the name you are assigning to dynamically like that. You can, however, insert them in to a dictionary where the keys are your dynamically generated names.
characters = {}
for char_data in char_list:
characters[char_data[1]] = CharInStageList(*char_data)
Though if all your character objects are storing is name and id, it might make more sense to simplify the whole thing and just create mapping dictionaries rather than objects.
character_names_by_id = dict(char_data)
character_ids_by_name = dict((name, id) for id, name in char_data)
#chepner's answer is a great one if you can use the *args form to fill out your class instances.
If you're just asking the most efficient way to do this from a loop, remember you can have iterate over the parts of a tuple together:
data = ((1,'Tim'),(4,'Struppi'),(5,'Nami'),(6,'Luffy'))
class_data = (CharInStageList(id, name) for id, name in data) # "for id, name" will yield two vals
You can also use map, which is very common for doing bulk data translations. A common way to do it is with a lambda so you can write it clearly:
to_char = lambda k: CharInStageList(k[0], k[1])
class_data = map(to_char, data)
If you're doing something as simple as your example, you might not want to bother with your own class. the namedtuple is a great data structure for creating tuples that are easy to work with. It also means you can use positional or named args interchangeably, just as in #chepner's *args version:
StageListChar = namedtuple('CharInStageList', ['id', 'name'])
class_data = map(StageListChar , data)
I have a form with ModelChoiceField field which can be empty (assuming "All selected") or has a certain value.
What is the best way to create a single QuerySet which select all records or some records without using if-else statement?
Currently I am using something like this:
Form:
class MyForm(forms.Form):
operators = forms.ModelChoiceField(
queryset=EpayOperators.objects.all(),
label="Choose operator",
empty_label="All",
required=False)
...
View:
# Any security and validation checks are ommited
...
if request.POST.get('operators'):
# Some operator was selected
payments = Transactions.objects.filter(date=my_date, sum=my_sum, operator=MyForm.cleaned_data['operators'])
else:
# None selected, display all operators
payments = Transactions.objects.filter(date=my_date, sum=my_sum)
I feeling that this is not the pythonic way to do it: to use if-else with two separates queries.
Is there any way to create a single, versatile, query?
What is the proper way to do it?
Thank you.
You can do:
filter_args = {}
filter_args['date'] = my_date
filter_args['sum'] = my_sum
if request.POST.get('operators'):
filter_args['operator'] = MyForm.cleaned_data['operators']
payments = Transactions.objects.filter(**filter_args)
However your code is clear as it is so I'm not sure this improves your code.
Okay here is the problem. I have this code
list_categories = [None,"mathematics","engineering","science","other"]
class Books(db.Model)
title = db.StringProperty(required=True)
author = db.StringProperty()
isbn = db.StringProperty()
categories = db.StringListProperty(default=None, choices = set(list_categories))
what i want to do here is have my book.categories be a SUBSET of list categories, for example
i have a book whose categories should be 'engineering' and 'mathematics', but when I set
book.categories = ['engineering','mathematics']
it webapp2 gives me an error
BadValueError: Property categories is ['engineering','mathematics']; must be one of set([None,"mathematics","engineering","science","other"])
My initial guess here is that i must set my list_choices to be a POWERSET of [None,"mathematics","engineering","science","other"], but this is too inefficient.
Does anyone know a workaround for this?
The reason for the error (as I'm sure you've guessed) is that StringListProperty does not do any special handling of the choices keyword argument - it simply passes it along to the ListProperty constructor, which in turn passes it to the Property constructor, where it is evaluated:
if self.empty(value):
if self.required:
raise BadValueError('Property %s is required' % self.name)
else:
if self.choices:
match = False
for choice in self.choices:
if choice == value:
match = True
if not match:
raise BadValueError('Property %s is %r; must be one of %r' %
(self.name, value, self.choices))
The issue is that it iterates through each choice individually, but it is comparing it to your entire list (value), which will never result in a match since a string won't equal a list (again, you know this :) ).
My suggestion would be to modify how you assign the list to the property. For instance, instead of:
book.categories = ['engineering','mathematics']
Try something like this:
for category in ['engineering','mathematics']:
book.categories.append(category)
Since the ListProperty contains a list, you can append each item individually so that it passes the test in the aforementioned code. Note that in order to get this to work in my tests, I had to set up the model in a slightly different way - however if you can get to the error you mentioned above, then the append method should work fine.
It makes it a little less straightforward, I agree, but it should circumvent the issue above and hopefully work.
Create a many to many relationship using list of keys. Use the categories property in class Book as a list of keys of class Category.
class Book(db.Model)
title = db.StringProperty(required=True)
author = db.StringProperty()
isbn = db.StringProperty()
# List Of Keys
categories = db.ListProperty(db.Key)
class Category(db.Model)
name = db.StringProperty(choices = ('science', 'engineering', 'math'))
For more info and code samples about modeling check out: https://developers.google.com/appengine/articles/modeling