DRF: internal relationship within rested objects, expressed as SlugRelatedFIelds. Impossible? - django

Lets say I have three related models - Container, Category and Item. Each model has two string properties - a name and an uuid (as well as the normal "id" field.) The uuid fields will be used as SlugRelatedFields to express relationships in the REST interface. A Container contains a set of items and a set of categories; each item is related to a category.
I'd like to create an entire container (with all its items and categories) in one POST. But I can't seem to create a serializer that allows that. When I try to do so, I get an error when the serializer tries to decode an item - it finds that the referenced category doesn't exist yet. Which is not surprising, since the same POST is going to create it.
In JSON form, here's what I'd like to POST:
{
"name": "My Container",
"uuid": "<container-uuid>",
"categories": [
{
"name": "Category One",
"uuid": "<category-one-uuid>",
}
],
"items": [
{
"name": "Item One",
"uuid": "<item-one-uuid>",
"category": "<category-one-uuid>",
},
{
"name": "Item Two",
"uuid": "<item-two-uuid>",
"category": "<category-one-uuid>",
}
]
}
So that example would create one container, one category and two items - both items would be related to the same category.
I can use a SlugRelatedField in the Item serializer to identify the associated Category by uuid. But in this scenario, that throws an error:
Object with uuid=<category-one-uuid> does not exist.
while deserializing "Item One". Understandably, since that category is not yet in the database.
Is there some way to make this work? Can I override some method in the ItemSerializer to make it look for the associated Category in the Container that is being deserialized, rather than looking at the Categories that already exist in the database? I'm thinking that a solution may require a couple of things:
Ensuring that the categories are de-serialized before the items. What determines that? The order of the "fields" attribute in the serializer?
Using the deserialized categories a create a queryset that is used during deserialization of the items, replacing the database query that is normally used.
Any ideas?
Update 2014-08-22:
I haven't fixed this problem, but I've worked around it. The workaround is a filthy hack, involving a number of parts:
In the model, I changed the definition of the foreignkey reference
from item to category to allow NULL.
In the serializer for Item, I
declared the category as a simple CharField(), rather than a
SlugRelatedField that identifies the Category by its UUID.
In that serializer's "restore_object" method, I grab the specified UUID out of the category field (in the "attrs" dictionary passed to that method). I remove the entry from that dictionary and add an entry to a dictionary that maps Item UUIDs to the associated Category UUIDs. That dictionary is held in a per-request cache, as described here: Per-request cache in Django?
Then I have a handler for the "pre_save" signal on item, and it looks up the required category UUID in that dictionary (using the Items's UUID as key), does a query on the Category table to find the right item and uses that to set the foreignkey field.
So, when a request comes in to create a new Container with associated Items and Categories:
Because the Category UUID reference in the Item is just a CharField, it is NOT verified against a query of the existing categories in the database. So the serializer proceeds to saving stuff in the database.
The Categories are saved to the DB as normal. (They get saved first, presumably just because of the order of the Seriliazer's "fields" array.)
When the "restore_object" method is used to construct the Item that will be saved, the UUID of the required Category is stashed away for later use, and that "CharField" value is discarded from the fields used to construct the Item.
When the Item is about to be saved, the signal handler retrieves the stashed UUID and uses it to fetch the correct (newly-created) category to fill out the foreignkey field.
Hacky as heck, right? But it seems to work. I'd love to rework this one day to use a more elegant solution but in the real world, that won't happen.

Here is my idea:
go to rest_framework/serializers.py
look for the save_object method from the ModelSerializer class
there, you should find a piece of code like this one:
if getattr(obj, '_nested_forward_relations', None):
# Nested relationships need to be saved before we can save the
# parent instance.
for field_name, sub_object in obj._nested_forward_relations.items():
if sub_object:
self.save_object(sub_object)
setattr(obj, field_name, sub_object)
My best guess is that here the serializer tries to save your nested objects (categories and items).
put a breakpoint there and try to figure out how exactly those objects are created (in what order)
if you find anything interesting, you can update your post, and I'll try and help you further.
Good luck!

Related

django view filter ManyToMany field to guard view

I have a tag model, with ManyToMany field "parents" to tag model, to itself. There is also "allowed_users" field.
I need to guard a view in such a way, that the user won't see any tags in parents field, to which he is not allowed.
I try to modify queryset, removing the corresponding tags from parents. But when I change the instance, tag_instance.parents.set(my_new_list) it gets saved automatically so I'm altering the database and changing the real value of the instance.
So the general question is "how to guard my view in such a way, that object's ManyToMany field is filtered by custom logic".
Another question is "how to set manytomany field without altering database", as this would be a solution to the former one.
yes I use DRF
You can use the Prefetch object to filter related table when SQL queries are executed.
For instance:
Tag.objects.preftech_related(Prefetch("parents", queryset=Tag.objects.filter(allowed_users=current_user).distinct()))
This will prefetch "parents" (meaning no additionnal sql query will run when accessing my_tag.parent.all()) and filter parents to keep only those with the current user in allowed_user
Note: Tag.objects.filter(allowed_users=current_user) will duplicate tag entries for each user in allowed_user, ence the .distinct() to keep one of each

Change one django form field value relative to another

Let say I have two fields in a django form country and state.I want the values of state to relatively change with the values of country.i.e. I want the state field to list out the states of the country that user has selected. Also the state field should be empty during form initiation.I know that this can be done using java script and other scripts.But,I would like to know if there are any conventional methods exists in django to do the same.???
Sounds like you need to create a model for Country and State.
State model should have a foreign key linking to Country. This means many states can be related to one country. Then, populate the tables with all countries and states you want.
In your form, you can override the 'init' method with custom behavior. So, if you have declared a field 'state' then you can do something like self.fields['state'].choices = State.object.filter(country_id=some_country_id). This assumes you have some_country_id already and you can pass this through as a kwarg during instantiation.

Django annotate a field value to queryset

I want to attach a field value (id) to a QS like below, but Django throws a 'str' object has no attribute 'lookup' error.
Book.objects.all().annotate(some_id='somerelation__id')
It seems I can get my id value using Sum()
Book.objects.all().annotate(something=Sum('somerelation__id'))
I'm wondering is there not a way to simply annotate raw field values to a QS? Using sum() in this case doesn't feel right.
There are at least three methods of accessing related objects in a queryset.
using Django's double underscore join syntax:
If you just want to use the field of a related object as a condition in your SQL query you can refer to the field field on the related object related_object with related_object__field. All possible lookup types are listed in the Django documentation under Field lookups.
Book.objects.filter(related_object__field=True)
using annotate with F():
You can populate an annotated field in a queryset by refering to the field with the F() object. F() represents the field of a model or an annotated field.
Book.objects.annotate(added_field=F("related_object__field"))
accessing object attributes:
Once the queryset is evaluated, you can access related objects through attributes on that object.
book = Book.objects.get(pk=1)
author = book.author.name # just one author, or…
authors = book.author_set.values("name") # several authors
This triggers an additional query unless you're making use of select_related().
My advice is to go with solution #2 as you're already halfway down that road and I think it'll give you exactly what you're asking for. The problem you're facing right now is that you did not specify a lookup type but instead you're passing a string (somerelation_id) Django doesn't know what to do with.
Also, the Django documentation on annotate() is pretty straight forward. You should look into that (again).
You have <somerelation>_id "by default". For example comment.user_id. It works because User has many Comments. But if Book has many Authors, what author_id supposed to be in this case?

Django append to JSON after serializers.serialze has been run on a queryset

I am returning a JSON serialized queryset using the following queryset:
genome_parents = Genome.objects.filter(genes=cus_id)
where cus_id is the FK pointing to a companies table so I am retrieving all Genome objects related to the current working company. I return this data after a form has been posted via:
genome_parents = serializers.serialize('json', genome_parents, use_natural_keys=True)
However, I need the natural key for one of my foreign keys, but the id for another (both on the same model). So one field is displayed nicely, but the other isn't. So this does what I need except for one little thing, I need the plain id number so that I can pre-populate my FK form field.
One thought I had was to just append something like
genome_parents.append({'id':gene.id})
but that obviously doesn't work. Is there anyway I can augment the JSON so that I can include one more little piece of data (or change how I format the JSON)?
Greg
Just switch the order of the operations. And put the entire gene object into the list so it is properly serialized.
genome_parents = list( Genome.objects.filter(genes=cus_id) )
genome_parents.append(gene)
json_genome_parents = serializers.serialize('json', genome_parents, use_natural_keys=True)

Django DB, finding Categories whose Items are all in a subset

I have a two models:
class Category(models.Model):
pass
class Item(models.Model):
cat = models.ForeignKey(Category)
I am trying to return all Categories for which all of that category's items belong to a given subset of item ids (fixed thanks). For example, all categories for which all of the items associated with that category have ids in the set [1,3,5].
How could this be done using Django's query syntax (as of 1.1 beta)? Ideally, all the work should be done in the database.
Category.objects.filter(item__id__in=[1, 3, 5])
Django creates the reverse relation ship on the model without the foreign key. You can filter on it by using its related name (usually just the model name lowercase but it can be manually overwritten), two underscores, and the field name you want to query on.
lets say you require all items to be in the following set:
allowable_items = set([1,3,4])
one bruteforce solution would be to check the item_set for every category as so:
categories_with_allowable_items = [
category for category in
Category.objects.all() if
set([item.id for item in category.item_set.all()]) <= allowable_items
]
but we don't really have to check all categories, as categories_with_allowable_items is always going to be a subset of the categories related to all items with ids in allowable_items... so that's all we have to check (and this should be faster):
categories_with_allowable_items = set([
item.category for item in
Item.objects.select_related('category').filter(pk__in=allowable_items) if
set([siblingitem.id for siblingitem in item.category.item_set.all()]) <= allowable_items
])
if performance isn't really an issue, then the latter of these two (if not the former) should be fine. if these are very large tables, you might have to come up with a more sophisticated solution. also if you're using a particularly old version of python remember that you'll have to import the sets module
I've played around with this a bit. If QuerySet.extra() accepted a "having" parameter I think it would be possible to do it in the ORM with a bit of raw SQL in the HAVING clause. But it doesn't, so I think you'd have to write the whole query in raw SQL if you want the database doing the work.
EDIT:
This is the query that gets you part way there:
from django.db.models import Count
Category.objects.annotate(num_items=Count('item')).filter(num_items=...)
The problem is that for the query to work, "..." needs to be a correlated subquery that looks up, for each category, the number of its items in allowed_items. If .extra had a "having" argument, you'd do it like this:
Category.objects.annotate(num_items=Count('item')).extra(having="num_items=(SELECT COUNT(*) FROM app_item WHERE app_item.id in % AND app_item.cat_id = app_category.id)", having_params=[allowed_item_ids])