django - multiple queries into one - django

I want to find the Records with a certain tag within 100 mile radius. I have two queries that work independently (see below) but I don't know how to put them together.
Also Records model has a foreign key pointing to the GeoLocation model called geo_location. I want to be able to show fields from both models (Records and GeoLocation) in one shot. I tried with .select_related() on the GeoLocation query below but for some reason I only get it to show the GeoLocation model fields and not the additional Records model fields as I expected.
tag_search = Records.objects.filter(tags__slug__in=[tag])
geo_search = GeoLocation.objects.select_related().filter(srid2163__distance_lte=(pnt, D(mi=100))).distance(pnt)
Any ideas?
These are my models:
from taggit.managers import TaggableManager
from django.contrib.gis.db import models
class GeoLocation (models.Model):
lat = models.FloatField(blank=True)
long = models.FloatField(blank=True)
srid2163 = models.PointField(blank=True,srid=2163)
server_time = models.DateTimeField(auto_now_add=True)
objects = models.GeoManager()
def __unicode__(self):
return u'%s %s %s' % (self.lat, self.long, self.server_time)
class Records(models.Model):
title = models.CharField(blank=True, max_length=50)
message_body = models.TextField()
server_time = models.DateTimeField(auto_now_add=True)
geo_location = models.ForeignKey(GeoLocation, related_name='geoloc')
tags = TaggableManager()
def __unicode__(self):
return u'%s %s %s' % (self.title, self.message_body, self.server_time)
For the tags field in the Records model I'm using django-taggit.

There are two things wrong here.
Firstly, you've misunderstood what select_related() does. It doesn't bring the fields from the related model into the current one. Instead, it just pre-fetches the related instance, so that doing the model_instance.foreignkey_field.field_on_related_model doesn't cause another db hit.
Secondly, your models contradict what you originally said about the foreignkey. You said it was from GeoLocation to Records, but the model definitions show that is the other way round. select_related does not work in that direction - there is no way, given your current models, to query GeoLocation and get the related Records in one go. However, you can query Records and get the related GeoLocations.
(Thirdly, your answer to my comment about the use of the single-element in lookup is completely irrelevant. Use tags_slug=tag.)

Related

select_related with reverse foreign keys

I have two Models in Django. The first has the hierarchy of what job functions (positions) report to which other positions, and the second is people and what job function they hold.
class PositionHierarchy(model.Model):
pcn = models.CharField(max_length=50)
title = models.CharField(max_length=100)
level = models.CharField(max_length=25)
report_to = models.ForeignKey('PositionHierachy', null=True)
class Person(model.Model):
first_name = models.CharField(max_length=50)
last_name = models.CharField(max_length=50)
...
position = models.ForeignKey(PositionHierarchy)
When I have a Person record and I want to find the person's manager, I have to do
manager = person.position.report_to.person_set.all()[0]
# Can't use .first() because we haven't upgraded to 1.6 yet
If I'm getting people with a QuerySet, I can join (and avoid a second trip to the database) with position and report_to using Person.objects.select_related('position', 'position__reports_to').filter(...), but is there any way to avoid making another trip to the database to get the person_set? I tried adding 'position__reports_to__person_set' or just position__reports_to__person to the select_related, but that doesn't seem to change the query. Is this what prefetch_related is for?
I'd like to make a custom manager so that when I do a query to get Person records, I also get their PositionHeirarchy and their manager's Person record without more round trips to the database. This is what I have so far:
class PersonWithManagerManager(models.Manager):
def get_query_set(self):
qs = super(PersonWithManagerManager, self).get_query_set()
return qs.select_related(
'position',
'position__reports_to',
).prefetch_related(
)
Yes, that is what prefetch_related() is for. It will require an additional query, but the idea is that it will get all of the related information at once, instead of once per Person.
In your case:
qs.select_related('position__report_to')
.prefetch_related('position__report_to__person_set')
should require two queries, regardless of the number of Persons in the original query set.
Compare this example from the documentation:
>>> Restaurant.objects.select_related('best_pizza')
.prefetch_related('best_pizza__toppings')

Django admin list_display not showing several objects

I have just begun to play around with Django admin views, and to start off, I am trying to do something very simple: showing several fields in the listing of objects using list_display as explained here: https://docs.djangoproject.com/en/dev/ref/contrib/admin/
This is my dead simple code:
class ArticleAdmin(admin.ModelAdmin):
list_display = ('title', 'category')
Unfortunately, the list_display option is causing the columnar view to appear, but only some of the objects (40 out of 85) are now displaying in the listing. I cannot deduce why certain objects are showing over the others - their fields look like they are filled similarly. It's clearly not paginating, because when I tried it on an admin of another model, it showed only 2 objects out of about 70 objects.
What might be going on here?
[UPDATE] Article Model:
class Article(models.Model):
revision = models.ForeignKey('ArticleRevision', related_name="current_revision")
category = models.ForeignKey('meta.Category')
language = models.ForeignKey('meta.Language', default=get_default_language)
created = models.DateTimeField(auto_now_add=True, editable=False)
changed = models.DateTimeField(auto_now=True, editable=False)
title = models.CharField(max_length=256)
resources = models.ManyToManyField('oer.Resource', blank=True)
image = models.ManyToManyField('media.Image', blank=True)
views = models.IntegerField(editable=False, default=0)
license = models.ForeignKey('license.License', default=get_default_license)
slug = models.SlugField(max_length=256)
difficulty = models.PositiveIntegerField(editable=True, default=0)
published = models.NullBooleanField()
citation = models.CharField(max_length=1024, blank=True, null=True)
Before adding list_display:
After adding list_display:
[UPDATE] This behaviour occurs only when ForeignKey fields are included in list_display tuple. Any of them.
[UPDATE] Category model code:
class Category(models.Model):
title = models.CharField(max_length=256)
parent = models.ForeignKey('self')
project = models.NullBooleanField(default=False)
created = models.DateTimeField(auto_now_add=True, editable=False)
slug = models.SlugField(max_length=256, blank=True)
def __unicode__(self):
return self.title
This behavior is caused by a foreign key relation somewhere that is not declared as nullable, but nonetheless has a null value in the database. When you have a ManyToOne relationship in list_display, the change list class will always execute the query using select_related. (See the get_query_set method in django.contrib.admin.views.ChangeList).
select_related by default follows all foreign keys on each object, so any broken foreign key found by this query will cause data to drop out when the query is evaluated. This is not specific to the admin; you can interactively test it by comparing the results of Article.objects.all() to Article.objects.all().select_related().
There's no simple way to control which foreign keys the admin will look up - select_related takes some parameters, but the admin doesn't expose a way to pass them through. In theory you could write your own ChangeList class and override get_query_set, but I don't recommend that.
The real fix is to make sure your foreign key model fields accurately reflect the state of your database in their null settings. Personally, I'd probably do this by commenting out all FKs on Article other than Category, seeing if that helps, then turning them back on one by one until things start breaking. The problem doesn't have to be with a FK on an article itself; if a revision, language or category has a broken FK that will still cause the join to miss rows. Or if something they relate to has a broken FK, etc etc.

Django model design: editable help text for individual model fields. Is there a foreign field that references a specific field of a model?

I have several models with several fields in my app. I want to set up a way for the user to be able to modify a help text system for each field in the model. Can you give me some guidance on how to design the models, and what field types to use? I don't feel right about storing the model and field name in CharFields, but if that is the only way, I may be stuck with it.
Is there a more elegant solution using Django?
For a quick and silly example, with an app named jobs, one named fun, and make a new app named helptext:
jobs.models.py:
class Person(models.Model):
first_name = models.CharField(max_length=32)
.
.
interests = models.TextField()
def __unicode__(self):
return self.name
class Job(models.Model):
name = models.CharField(max_length=128)
person = models.ForeignKey(Person)
address = models.TextField()
duties = models.TextField()
def __unicode__(self):
return self.name
fun.models.py:
class RollerCoaster(models.Model):
name = models.CharField(max_length=128)
scare_factor = models.PositiveInteger()
def __unicode__(self):
return self.name
class BigDipper(RollerCoaster):
max_elevation = models.PositiveInteger()
best_comment_ever_made = models.CharField(max_length=255)
def __unicode__(self):
return super.name
Now, let's say I want to have editable help text on Person.interests, and Job.duties, RollerCoaster.scare_factor, and BigDipper.best_comment_ever_made. I'd have something like:
helptext.models.py:
from django.contrib.contenttypes.models import ContentType
class HelpText(models.Model):
the_model = models.ForeignKey(ContentType)
the_field = models.CharField(max_length=255)
helptext = models.CharField(max_length=128)
def __unicode__(self):
return self.helptext
So, what is the better way to do this, other than making HelpText.the_model and HelpText.the_field CharFields that have to be compared when I am rendering the template to see if helptext is associated with each field on the screen?
Thanks in advance!
Edit:
I know about the help_text parameter of the fields, but I want this to be easily edited through the GUI, and it may contain a LOT of help with styling, etc. It would be HTML with probably upwards of 50-60 lines of text for probably 100 different model fields. I don't want to store it in the field definition for those reasons.
I changed the HelpText model to have a reference to ContentType and the field a CharField. Does this seem like a good solution? I am not sure this is the most elegant way. Please advise.
Edit 2013-04-19 16:53 PST:
Currently, I tried this and it works, but not sure this is great:
from django.db import models
from django.contrib.contenttypes.models import ContentType
# Field choices for the drop down.
FIELDS = ()
# For each ContentType verify the model_class() is not None and if not, add a tuple
# to FIELDS with the model name and field name displayed, but storing only the field
# name.
for ct in ContentType.objects.all():
m = ct.model_class()
if m is not None:
for f in ct.model_class()._meta.get_all_field_names():
FIELDS += ((f, str(ct.model) + '.' + str(f)),)
# HelpText model, associated with multiple models and fields.
class HelpText(models.Model):
the_model = models.ForeignKey(ContentType)
the_field = models.CharField(max_length=255, choices=FIELDS)
helptext = models.TextField(null=True, blank=True)
def __unicode__(self):
return self.helptext
Doesn't feel like the best, but please advise if this is a solution that will bite me in the behind later on and make me filled with regrets... :*(
The solution works, and I have it implemented, but you have to be aware that sometimes the ContentTypes get out of sync with your models. You can manually update the content types with this:
python manage.py shell
>>> from django.contrib.contenttypes.management import update_all_contenttypes
>>> update_all_contenttypes(interactive=True)
This allows you to add the new ones and remove the old ones, if they exist.
The nice thing about the Field not being a foreign key is that I can put anything in it for help text. So, say I have a field "First Name." I can put a helptext connected to the Person model and the "first_name" field. I can also make something up, like "Something really confusing." The helptext is now associated with the Person model and the "Something really confusing" field. So, I can put it at the top of the form, instead of associating to a field with hard foreign keying. It can be anything arbitrary and will follow with that "field" anywhere. The hangup would be that you may change the name of the helptext field association inadvertently sending your original helptext into never land.
To make this easy, I created a TemplateTag, which I pass the name of the model and the name of the "field" I want to associate. Then anytime the template is rendered, that helptext is there, editable for anybody to get assistance with their user interface forms.
Not sure this is the best solution, but I couldn't really see any other way to do it, and got no responses.
Cheerio!

django: how do I query based on GenericForeignKey's fields?

I'm new in using GenericForeignKey, and I couldn't make it to work in a query statement. The tables are roughly like the following:
class Ticket(models.Model):
issue_ct = models.ForeignKey(ContentType, related_name='issue_content_type')
issue_id = models.PositiveIntegerField(null=True, blank=True)
issue = generic.GenericForeignKey('issue_ct', 'issue_id')
class Issue(models.Model):
scan = models.ForeignKey(Scan)
A scan creates one issue, an issue generates some tickets, and I made Issue as a foreign key to Ticket table. Now I have a Scan object, and I want to query for all the tickets that related to this scan. I tried this first:
tickets = Tickets.objects.filter(issue__scan=scan_obj)
which doesn't work. Then I tried this:
issue = Issue.objects.get(scan=scan_obj)
content_type = ContentType.objects.get_for_model(Issue)
tickets = Tickets.objects.filter(content_type=content_type, issue=issue)
Still doesn't work. I need to know how to do these kind of queries in django? Thanks.
The Ticket.issue field you've defined will help you go from a Ticket instance to the Issue it's attached to, but it won't let you go backwards. You're close with your second example, but you need to use the issue_id field - you can't query on the GenericForeignKey (it just helps you retrieve the object when you have a Ticket instance). Try this:
from django.contrib.contenttypes.models import ContentType
issue = Issue.objects.get(scan=scan_obj)
tickets = Ticket.objects.filter(
issue_id=issue.id,
issue_ct=ContentType.objects.get_for_model(issue).id
)
Filtering across a GenericForeignKey can by creating a second model that shares the db_table with Ticket. First split up Ticket into an abstract model and concrete model.
class TicketBase(models.Model):
issue_ct = models.ForeignKey(ContentType, related_name='issue_content_type')
issue_id = models.PositiveIntegerField(null=True, blank=True)
class Meta:
abstract = True
class Ticket(TicketBase):
issue = generic.GenericForeignKey('issue_ct', 'issue_id')
Then create a model that also subclasses TicketBase. This subclass will have all the same fields except issue which is instead defined as a ForeignKey. Adding a custom Manager allows it to be filtered to just a single ContentType.
Since this subclass does not need to be synced or migrated it can be created dynamically using type().
def subclass_for_content_type(content_type):
class Meta:
db_table = Ticket._meta.db_table
class Manager(models.Manager):
""" constrain queries to a single content type """
def get_query_set(self):
return super(Manager, self).get_query_set().filter(issue_ct=content_type)
attrs = {
'related_to': models.ForeignKey(content_type.model_class()),
'__module__': 'myapp.models',
'Meta': Meta,
'objects': Manager()
}
return type("Ticket_%s" % content_type.name, (TicketBase,), attrs)

How to build SQL query with two left joins using django ORM

I need to build an MySQL query and I want to try with django ORM first and then use raw as last resort.
I found documentation on single JOIN or JOINs between two tables but there is no examples or at least a simple (beginner wise) explanation of JOINs between three tables
Content of models.py is
from django.db import models
# Create your models here.
class Threads(models.Model):
name = models.CharField(max_length=100)
author = models.CharField(max_length=100)
date = models.DateTimeField("date published")
slug = models.SlugField()
def __unicode__(self):
return self.name
class Posts(models.Model):
name = models.CharField(max_length=100)
text = models.TextField()
author = models.CharField(max_length=100)
date = models.DateTimeField("date published")
slug = models.SlugField()
def __unicode__(self):
return self.name
class Relations(models.Model):
thread = models.ForeignKey(Threads, related_name = "%(app_label)s_%(class)s_related")
post = models.ForeignKey(Posts, related_name = "%(app_label)s_%(class)s_related")
and this is SQL query in raw that I am trying to build
SELECT forum_threads.id AS t_id, forum_threads.name AS t_name, forum_threads.slug AS t_slug, forum_posts.*
FROM forum_threads
LEFT JOIN forum_relations ON forum_threads.id=forum_relations.thread_id
LEFT JOIN forum_posts ON forum_relations.post_id=forum_posts.id
WHERE forum_threads.slug="<slug_name>"
GROUP BY forum_threads.id
"forum" is my app name
Now I don't know if I need to tweak/change my Models and if, how. Note that I can change my models no important data whatsoever.
EDIT
Thank you for all your answers!
Ok I played a bit with various examples until i managed to produce someting. I got it like this:
thread = Threads.objects.filter(slug = slug)
posts = Posts.objects.filter(forum_relations_related__thread = thread[0].id)
first query is to retrieve id of thread from slug and second one returns all post related to thread on that thread id.
I'll try and play around with a M2M part since I have at least one working example.
Why not just use a M2M relation, you can use through if need be.
You could then get a thread by slug
thread = Threads.objects.get(slug=slug_name)
then you can access the posts related to a thread via
thread.posts_set.all()