I would like to create a dict or dict-like object based on a django orm query involving prefetched objects. Due to the number of objects involved, I would like to do as much of this in the database as possible.
To use the familiar example structure, if I have an Author and Books with the usual ForeignKey from Book to Author, and I also have an Edition with a ForeignKey back to Book, I would like to do something like
Author.objects.prefetch_related(Prefetch('book_set', queryset=Book.objects.filter(cycle__id=3).select_related('edition__binding'), to_attr="bindings")) # clearly this is wrong
and ultimately call .values() or something of the sort to get a dict-like result consisting of rows of authors, which should include an entry "bindings", which contains a list of bindings that each author has been published in. The stretch goal would be to have bindings be a semicolon-separated list, ie [{"Author": "Daniel Dennett", "bindings": "paper; hardback; cloth"}, {"Author": "Jemiah Jefferson", "bindings": "paper; zine"}]
So far, I have been able to get a field like "bindings" attached to the queryset using prefetch_related and select_related, as above, but this field is not included in the result of a call to .values(). This means I have to loop over the objects, which simply takes too long for my purposes (there are many objects, and the request times out)
Create custom a Concat annotation which will mimic MySQL GROUP_CONCAT function. Then you can use .values on the annotated bindings.
For Django 1.8 your Concat class can be something like this:
from django.db import models
class Concat(models.Aggregate):
# supports GROUP_CONCAT(DISTINCT field SEPARATOR str_val)
# do not support order_by
function = 'GROUP_CONCAT'
template = '%(function)s(%(distinct)s%(expressions)s SEPARATOR "%(separator)s")'
def __init__(self, expression, distinct=False, separator=None, **extra):
super(Concat, self).__init__(
expression,
distinct='DISTINCT ' if distinct else '',
separator=separator or '',
output_field=models.CharField(),
**extra)
While in Django 1.7
class Concat(models.Aggregate):
def add_to_query(self, query, alias, col, source, is_summary):
#we send source=CharField to prevent Django from casting string to int
aggregate = SQLConcat(col, source=models.CharField(), is_summary=is_summary, **self.extra)
query.aggregates[alias] = aggregate
#for mysql
class SQLConcat(models.sql.aggregates.Aggregate):
sql_function = 'group_concat'
#property
def sql_template(self):
if self.extra.get('separator'):
return '%(function)s(%(field)s SEPARATOR "%(separator)s")'
else:
return '%(function)s(%(field)s)'
Now you can do:
Author.objects.annotate(bindings=Concat('book__edition__binding')).values('name', 'bindings')
unfortunately this wont filter your books by cycle__id=3, but you can apply the filter before the annotation happens.
Author.objects.filter(book__cycle__id=3).annotate(bindings=Concat('book__edition__binding')).values('name', 'bindings')
This will strip from the result authors without book with cycle__id=3.
Related
When using DjangoListObjectType from graphene_django_extras, I can define a custom qs property on a SearchFilter.
The qs function has the object as its only argument, and through that I can get the request, and in turn the query string which includes the queried fields.
Before hacking together my own parser to get these fields, am I going about this the wrong way? Or is there something else out there?
The idea is to have quite a rigid approach, as we have 7 types of paginated list types with fields that result in a few unnecessary database hits, so we want to prefetch a few fields.
Graphene has a dataloader approach which kind of looks right, but more complicated than just prefetching them at the qs stage.
Re the dataloader approach, I tried something like this
class UserLoader(DataLoader):
def batch_load_fn(self, keys):
users = {user.id: user for user in User.objects.filter(id__in=keys)}
return Promise.resolve([users.get(user_id) for user_id in keys])
user_loader = UserLoader()
class BookType(DjangoObjectType):
...
author = graphene.Field(UserType)
def resolve_author(self, *args, **kwargs):
return user_loader.load(self.author.id)
Which kind of obviously feels completely wrong, but is what they do in the docs.
Using a DataLoader is the correct approach but do self.author_id instead of self.author.id.
By calling self.author.id you're fetching the author for each book (one SQL query per author) then getting the id attribute from each author.
When you add the author ForeignKey to your Book model, behind the scenes, Django adds a column to the books table—author_id. You can pass this value to your DataLoader and have it fetch all the corresponding Authors in a single SQL query using a SELECT IN.
https://docs.djangoproject.com/en/3.2/ref/models/fields/#database-representation
You'll notice that the Graphene Docs are doing the same thing with best_friend_id in their example.
with the new django-filter v2 coming out. I was wondering how i could replicate the below functionality with it.
Before my filterset was as so
class PostFilterSet(filters.FilterSet):
date = filters.DateFilter(
name='date',
lookup_expr=DATETIME_LOOKUPS,
method='filter_date')
class Meta:
# Meta Options here
def filter_date(self, queryset, name, value):
# filter logic
What this would essentially let me do on the api is make filter request like this 'https://example.com/api/post/?date__gt=exampledate' but with the latest update i can no longer keep the list of DATETIME_LOOKUPS and have to use the Lookup choice filter which changed the request to 'https://example.com/api/post/?date=exampledate&date_lookup=exact' is there a way to make it work like it did before in a painless way ?
I'm using Django 1.8 with django-rest-framework v3.2.2. I have a query that involves raw SQL:
#api_view(['GET'])
def total_spending(request, format=None):
code = request.query_params.get('code', None)
query = 'SELECT * FROM vw_presentation_summary WHERE code=%s"
cursor = connection.cursor()
cursor.execute(query, tuple([code]))
cursor.close()
My question is how to take this cursor and turn it into a data object that I can pass to django-rest-framework's Response.
Right now I'm doing it manually, which works OK:
def dictfetchall(cursor):
"Returns all rows from a cursor as a dict"
desc = cursor.description
return [
dict(zip([col[0] for col in desc], row))
for row in cursor.fetchall()
]
def total_spending(request, format=None):
...
return Response(dictfetchall(cursor))
But would it be better to use a Serializer somehow? I'm not clear if Serializers do anything useful other than define the fields you want to return.
Unless you're dealing with some complicated (including nested) representation of your model objects, a serializer is overkill if you're only going to use it for serializing objects. As you've already noticed, all of your fields can be natively serialized without any extra steps.
Serializers are great for shaping your output (renaming fields, grouping them with nested serializers) and doing it consistently. This is especially true when working with Django models, because Django doesn't natively serialize model objects down to Python dictionaries, it prefers the actual model object.
The power of a serializer comes in the deserialization, where it can map fields across models and build out relations for you. It can also do validation across all of these relations, which is something that would usually take a while to do manually.
I've been looking through the Haystack documentation on multiple indexes, but I can't figure out exactly how to use them.
The main model in this example is Proposal. I want to have two search indexes that return a list of proposals: one that only searches in the proposals themselves, and one that searches in proposals along with their comments. I've set up search_indexes.py like this:
class ProposalIndexBase(indexes.SearchIndex, indexes.Indexable)
title = indexes.CharField(model_attr="title", boost=1.1)
text = indexes.NgramField(document=True, use_template=True)
date = indexes.DateTimeField(model_attr='createdAt')
def get_model(self):
return Proposal
class ProposalIndex(ProposalIndexBase):
comments = indexes.MultiValueField()
def prepare_comments(self, object):
return [comment.text for comment in object.comments.all()]
class SimilarProposalIndex(ProposalIndexBase):
pass
Here's my search in views.py:
def search(request):
if request.method == "GET":
if "q" in request.GET:
query = str(request.GET.get("q"))
results = SearchQuerySet().all().filter(content=query)
return render(request, "search/search.html", {"results": results})
How do I set up a separate view that gets a SearchQuerySet from a specific index?
The Haystack (and other auto-generated) documentation is not a good example of clarity and it's about as exciting as reading a phone book. I think the section you referred to on "Multiple Indexes" is actually about accessing different backend search engines (like whoosh, solr, etc.) for queries.
But your question seems to be about how to query the "SearchIndexes" for different models. In your example, you want to have one search query for "proposals" and another one for "proposals" + "comments," if I understand your question correctly.
I think you want to look at the SearchQuerySet API, which describes how to filter the queryset returned by the search. There is a method called models that allows you to supply a model class or a list of model classes to limit the queryset results.
For example, in your search view, you may want to have a query string parameter for, say, "content" that specifies whether the search is for "proposals" or for "everything" (proposals and comments). So your frontend needs to supply the extra content parameter when calling the view (or you can use separate views for the different searches).
Your query strings need to look something like:
/search/?q=python&content=proposal
/search/?q=python&content=everything
And your view should parse the content query string parameter to get the model(s) for filtering the search query results:
# import your model classes so you can use them in your search view
# (I'm just guessing these are what they are called in your project)
from proposals.models import Proposal
from comments.models import Comment
def search(request):
results = None
if request.method == "GET":
if "q" in request.GET:
query = str(request.GET.get("q"))
# Add extra code here to parse the "content" query string parameter...
# * Get content type to search for
content_type = request.GET.get("content")
# * Assign the model or models to a list for the "models" call
search_models = []
if content_type is "proposal":
search_models = [Proposal]
elif content_type is "everything":
search_models = [Proposal, Comment]
# * Add a "models" call to limit the search results to the particular models
results = SearchQuerySet().all().filter(content=query).models(*search_models)
return render(request, "search/search.html", {"results": results})
If you have a lot of search indexes (i.e. a lot of content from many models), you might not want to hardcode the models in your view but use the get_model function from django.db to fetch the model class dynamically.
I am writing a django-nonrel based app and using admin view functionality provided by Django.
I want a Many-to-many relationship between two models and for this, I am using ListField found inside djangotoolbox.fields
To provide a custom form field, I have overridden ListField with another class ModelListField as described in this question which overrides formfield function to return a MultipleChoiceField form widget.
The view part works fine but I am unable to save the model using sqlite3 backend.
Assume, two models (Standard Many-to-many relationship between note and tags)
from django.db import models
class Tag(models.Model):
name = models.CharField(max_length=255)
class Note(models.Model):
tags = ModelListField(db.ForeignKey(Tag))
With these change, add note page appears correctly in admin interface but when I try to save the note, I get the following exception:
InterfaceError, Error binding parameter 0 - probably unsupported type.
inside django\db\backends\sqlite3\base.py in execute, line 234
The line 234 reads as follows:
class SQLiteCursorWrapper(Database.Cursor):
"""
Django uses "format" style placeholders, but pysqlite2 uses "qmark" style.
This fixes it -- but note that if you want to use a literal "%s" in a query,
you'll need to use "%%s".
"""
def execute(self, query, params=()):
query = self.convert_query(query)
try:
234 ----> return Database.Cursor.execute(self, query, params)
except Database.IntegrityError, e:
raise utils.IntegrityError, utils.IntegrityError(*tuple(e)), sys.exc_info()[2]
except Database.DatabaseError, e:
raise utils.DatabaseError, utils.DatabaseError(*tuple(e)), sys.exc_info()[2]
And query & params passed to call are:
query: 'INSERT INTO "note" ("tags") VALUES (?)'
params: [1, 2, 3]
where 1, 2, 3 are pk of three tag fields in database.
When creating model, I specify the COLUMN TYPE of tags as "ListField" i.e.
CREATE table SQL query is:
CREATE TABLE "notes" (
"id" integer NOT NULL PRIMARY KEY,
"tags" ListField NOT NULL
)
Database query execute call above barfs when it sees the list [1,2,3].
Instead of list, I tried to pass the list as smart_unicode(list) or just a string "1 2 3" but then ListField throws error while validating (in get_db_prep_value) because it expects list.
I am unable to understand whether it is correct to pass the list object to Database.Cursor.execute call above or something is missing as ListField is correctly expecting list but when writing to database, someone should have converted this list into string etc..?
There's no good example of how to use ListField :-(
Thanks for reading a long & boring description..
I figured out it is the responsibility of get_db_prep_save to prepare data in correct format for saving to database.
So, in ModelListField, I override get_db_prep_save and convert it to string to fix the "Unsupported type" error.
def get_db_prep_save(self, value, connection):
retval = super(ModelListField, self).get_db_prep_save(value)
return unicode(retval)
I also faced an issue with Select Widget not showing tags with pk > 10 as selected.
Inside django-non 1.3,
I had to add eval in below function:
class Select(Widget):
def render_options(self, choices, selected_choices):
**if(type(selected_choices) != list):
selected_choices = eval(selected_choices)**
This was done because render_option was called with list as string i.e. "[1,2,3]" instead of just [1,2,3], so string was converted back to list using eval.
Not sure if these two are related or latter issue is bug with django-nonrel.
As dragonx said, maybe it's not a good idea to test with sqlite3 and django-nonrel.
I will change my backend soon.