How do I add choices to google datastore StringListProperty? - python

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

Related

assign list of string to a taggable field

I have a list s where,
s = 'The name of my country is Bangladesh'
I converted the string into list of strings like below:
ss = s.split()
Again, in my database model I have a field keywords
keywords = TaggableManager(_('keywords'), blank=True, help_text=keywords_help_text)
I want to assign the list of string ss to the field keywords. I tried below:
keywords = ss
but I got error. I want to assign the list of string to my taggfield.
Seems that you are doing it the wrong way.
You have some class in which you have this keyword field which is efffectively a many to many relationship with another model (something like 'Tags')
How you use it is:
lets say the class is Fruits then you will have to take the instance of that class say Mango(the object you want to create keywords for)
Then you use:
Mango.keywords.add(ss)
or
self.keywords.add(ss)

Force django-autocomplete=light to return 'best' matches first

I have a long list of items I am searching through with a django-autocomplete-light field, with the autocomplete defined as such:
class OccupationAutocomplete(autocomplete_light.AutocompleteModelBase):
model = models.Occupation
search_fields=['title',]
Some job titles are alphabetically quite far through the list, like for example:
"teacher"
which come after other 'less ideal titles', like:
"Agricultural teacher", "Architecture teacher", "Building teacher", etc...
What I would like is for the "best" match, either closest or just match that starts with the text of the search, so if someone searches for "teach", they get "teacher", as it starts with the same letters, and then other less accurate matches after.
I've tried setting search_fields with a preferred order
search_fields=['^title','title',]
but an analysis of the autocomplete code shows that the terms are all munged into one query before being returned.
How would I go about ordering this list in a more appropriate way?
In the end I had to implement a new class, this just accepts a list of dictionaries in order of 'preferred weight' and then returns results based on that.
class OrderedAutocomplete(autocomplete_light.AutocompleteModelBase):
ordered_search_fields = []
def choices_for_request(self):
"""
Return a queryset based on :py:attr:`choices` using options
:py:attr:`split_words`, :py:attr:`search_fields` and
:py:attr:`limit_choices`.
"""
assert self.choices is not None, 'choices should be a queryset'
q = self.request.GET.get('q', '')
exclude = self.request.GET.getlist('exclude')
base_split_words = self.split_words
_choices = []
for weight,opt in enumerate(self.ordered_search_fields):
if len(_choices) >= self.limit_choices:
break
search_fields = opt['search_fields']
self.split_words = opt['split_words'] or base_split_words
conditions = self._choices_for_request_conditions(q,
search_fields)
choices = self.order_choices(self.choices.filter(conditions).exclude(pk__in=exclude))
if choices.count():
_choices += list(choices)[0:self.limit_choices]
return _choices[0:self.limit_choices]
This can then be instantiated using:
class OccupationAutocomplete(OrderedAutocomplete):
model = models.Occupation
ordered_search_fields = [
{'search_fields': ['^title'], 'split_words':False},
{'search_fields': ['title'], 'split_words':True},
]
autocomplete_light.register(OccupationAutocomplete)

Django: Sort object by string attribute non-alphabetically

So, in my Django.models I have a class as the following:
class Basicorderbook(models.Model):
ordernumber = models.ForeignKey('Idbook', db_column='OrderNumber', related_name='BasicOrder_IDNumber', primary_key=True) # Field name made lowercase.
username = models.ForeignKey('Userbook', db_column='Username', to_field='username', related_name='BasicOrder_Username') # Field name made lowercase.
price = models.FloatField(db_column='Price') # Field name made lowercase.
volume = models.FloatField(db_column='Volume') # Field name made lowercase.
type = models.CharField(db_column='Type', max_length=20) # Field name made lowercase.
...
def __unicode__(self):
return unicode(self.ordernumber.idnumber)
...
and a view as the following to return an object containing multiple "Basic Orders":
class BuyOrdersView(generic.ListView):
model = models.Basicorderbook
template_name = 'Blank.html'
context_object_name = 'buy_order_list'
def get_queryset(self):
"""
Returns the 5 most viable buy orders
"""
base_buy_order_list = models.Basicorderbook.objects.using('exchange').filter(
action = 'Buy',
#price > 520,
#active = 1
)
volume_buy_order_list = base_buy_order_list.order_by('-volume')
#This line here in question (I know this won't work, it's just an example)
type_buy_order_list = volume_buy_order_list.order_by('Limit' in 'type')
price_buy_orders = type_buy_order_list.order_by('-price')
buy_order_list = price_buy_orders[:15]
return buy_order_list
The "type" attribute of the object can be either "Limit", "Liquid", or "Conditional"
As shown, the problem lies in how I intend to sort the order object. I want to make it so that it is ordered with the following hierarchy:
Highest price first
If price is the same, have orders with "conditional" type first, then "limit", then "liquid"
If an order's price and type are the same, have orders sorted by volume
The numerical values (Price, Volume) are easily sortable by the handy order_by function, but when it comes to the string attributes I've come to a stop. It's not even an alphabetical order, so I can't do it that way. I see a few options that are simply annoying and inefficient workarounds:
Use a for loop to take each object and append into a list based on their string attribute
Create another attribute, "TypePriority", which I assign a numerical value (1, 2, 3) based on its priority in regards to the order type
So my TLDR question is, is there a way to sort objects based on a string value attribute in a non-alphabetic order? Also, will my object maintain it's sorting hierarchy with my current model, or should I join the order_by statements in a different way (Not sure if sorting by a certain attribute will "unsort" the new object I just created).
Note: the "_list" suffix is only for clarity, I know it's not a list :)
UPDATE: My current implementation that works is my workaround #1 example, but I'm wondering if there's way to sort it otherwise.
Per your text 'conditional' < 'limit' < 'liquid' so sorting on this should work.
Can you just do a three-way sort such as ('-price', 'type', 'volume') ?
Off course this assumes that the sort is not case sensitive or the input is modified at some point
D.
I forgot to update, but it turns out that there is no way to reorder the object as I wanted. I ended up doing the other reorganizations (by price, volume, etc.), but had to create a new list and check for the "type" attribute and sort them as such. It ended up looking like:
type_buy_order_list = []
for order in volume_buy_order_list:
if order[3] == "limit":
type_buy_order_list.append(order)
if order[3] == "liquid":
type_buy_order_list.append(order)
if order[3] == "conditional":
type_buy_order_list.append(order)

Django: updating ManyToManyField on save

I have a Book model which has a many-to-many field Author. If I merge two books, I want to make sure that the merged book has only unique authors, no doubles. I thought the best way to achieve this was to override the save() method, and so far I've come to this model:
class Book(models.Model):
authors = models.ManyToManyField()
def save(self):
authors_new = []
authors = self.authors.all()
for author in authors:
if author not in authors_new:
authors_new.append(author)
self.authors = authors_new #This won't work
super(Book, self).save()
The penultimate line obviously doesn't work, but I just can't seem to get the syntax right. I think what I want to achieve is pretty obvious, though. Anyone any idea what the right syntax is?
Edit 1:
To explain the merge: I must say I don’t fully understand the code (written by someone else - the merge takes places over several functions), so showing it here won’t help me. What it does is this: say there’s two books in the database that are obviously the same. The first book has
title= “Harry Potter and the Philosopher’s Stone”
author=“JK Rowling”
year=1999
and the other book has
title=“Harry Potter (book 1)”
author=“JK Rowling”
pages=320
When you merge you need to chose which book is the primary book. If I’d chose the first one to be primary, the merge should end up as
title=“Harry Potter and the Philosopher’s Stone”
author=“JK Rowling”
year=1999
pages=320
Problem is the merge ends up with author=“JK Rowling” twice I thought that I could take out the duplicates in the save function.
Edit 2:
The merge takes places in this function, that I haven't written:
def merge_model_objects(primary_object, *alias_objects):
# Use this function to merge model objects and migrate all of
# the related fields from the alias objects to the primary object.
for alias_object in alias_objects:
meta = alias_object._meta
for related_object in meta.get_all_related_objects():
alias_varname = related_object.get_accessor_name()
obj_varname = related_object.field.name
related_objects = getattr(alias_object, alias_varname)
for obj in related_objects.all():
if getattr(obj, 'do_not_merge', False):
continue
setattr(obj, obj_varname, primary_object)
obj.save()
related_objects = meta.get_all_related_many_to_many_objects()
for related_many_object in related_objects:
alias_varname = related_many_object.get_accessor_name()
obj_varname = related_many_object.field.name
if alias_varname is not None:
# standard case
related_many_objects = getattr(
alias_object, alias_varname).all()
else:
# special case, symmetrical relation, no reverse accessor
related_many_objects = getattr(alias_object, obj_varname).all()
for obj in related_many_objects.all():
getattr(obj, obj_varname).remove(alias_object)
getattr(obj, obj_varname).add(primary_object)
alias_object.delete()
primary_object.save()
return primary_object
This is quite a general function and can merge more than two objects, but if I merge Book 1 (= primary_object) and Book 2 (= alias_object) it will save it as a book with author="JK Rowling" twice.
Django represents many to many fields as sets. So you would probably want to do something like
self.authors = self.authors.union(other_model.authors)
Python sets are collections of unique, unordered objects.

Elegant Disjunctive Normal Form in Django

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)

Categories

Resources